Run the OpenCode AI coding agent entirely on your Android phone — inside a rootless Debian environment, with a fully native Kotlin / Jetpack Compose (Material 3 Expressive) client.
OpenCodePhone runs OpenCode — the open source AI coding agent — locally on your device:
- a full Debian (trixie) environment in app-private storage via proot — no root, no Termux;
- the OpenCode CLI (
opencode serve) installed inside that environment and exposed to the app over its HTTP API on localhost; - a native UI: Chat (sessions, streaming replies, tool activity, permission approvals), Files (workspace browser, editor, git clone/commit/push), Terminal (persistent shell in Debian), Settings (providers, models, server, git identity);
- an OpenCode-first model catalog: chat lists the models reported by the connected opencode server — standard and free models need no user-entered API key; optional custom provider keys are stored encrypted on-device;
- a remote mode that points the same UI at any machine running
opencode serve(handy for 32-bit devices — see limitations).
┌──────────────────────────────────────────────────┐
│ OpenCodePhone — Kotlin + Jetpack Compose │
│ Material 3 Expressive (light/dark themes) │
│ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ Chat │ │ Files+Git │ │ Terminal │ │
│ └────┬─────┘ └─────┬─────┘ └──────┬───────┘ │
│ └──────── service layer ────────┘ │
└────────────────────┬─────────────────────────────┘
│ HTTP localhost:4096 (REST + SSE)
┌────────────────────▼─────────────────────────────┐
│ Debian trixie (proot, rootless) │
│ ├── opencode serve (headless HTTP API) │
│ ├── git, curl, ripgrep, … │
│ └── /root/projects ◄── app-shared workspace │
└────────────────────┬─────────────────────────────┘
│
LLM API (your providers)
First launch performs a guided bootstrap: downloads Debian (digest-pinned, SHA-256 verified while streaming), installs tools, installs the OpenCode CLI (SRI-verified), configures the environment. Every design decision is recorded:
- Research: OpenCode platform
- Research: Debian on Android
- ADRs 001–008 (environment, native UI, installation, design system, ABI splits, terminal, targetSdk, toolset bootstrap)
Per-architecture APKs are built automatically:
| APK | Devices |
|---|---|
OpenCodePhone-vX.Y.Z-arm64-v8a.apk |
modern phones (recommended) |
OpenCodePhone-vX.Y.Z-armeabi-v7a.apk |
32-bit devices (terminal + remote mode only) |
OpenCodePhone-vX.Y.Z-x86_64.apk |
emulators, ChromeOS |
- Every push to
main→ the Release workflow itself tags the commit and publishes two signed builds:vX.Y.Z— stable release (marked Latest; this is exactly the channel the in-app updater installs from);vX.Y.Z-dev.N— signed dev pre-release built from the same commit (OpenCodePhone Dev, separate application id, installs side by side with the stable app; never selected by the in-app updater).
- Tags and Android version codes are derived automatically (latest
vX.Y.Ztag /versionName+ patch bump; versionCode = run number × 10), so each release updates cleanly over the previous one. Manual runs: Actions → Release → Run workflow (optionally pin a version); pushing anyvX.Y.Ztag builds exactly that version. - The digest-pinned
toolset-<arch>.tar.gzbundles live on thev0.1.1release (Pins.TOOLSET_URL_PREFIX); they are not rebuilt per release. Rebuild them withtools/build_toolset.py <arch>…and update the digests inPinswhen the toolset itself changes.
The native client supports OpenCode V1, pinned to 1.19.5. OpenCode V2 has a different API; its HTML web fallback is not a valid V1 health response. Since v0.1.3, a local server starts automatically after setup and on subsequent launches. Settings provides restart controls, errors and server logs.
Existing installations replace only the incompatible CLI, without re-extracting
Debian or deleting projects. V1 uses isolated XDG directories so it does not open
or overwrite a V2 database; V2 sessions/config are preserved but not converted.
See docs/adr/ADR-011-server-autostart-and-api-compatibility.md.
The terminal is a persistent pipe-based command console, not a PTY: no full-screen
TUI programs or job control. Normal shell commands and persistent cd work.
- "Environment self-check failed (exit 255)". This code alone does not
identify the cause. The preflight probe now logs proot's terminating signal.
SIGSYS (31 on arm64/x86_64) indicates a forbidden syscall, not battery
optimization. Debian glibc uses syscalls that Android app policies may reject,
including
set_robust_listandrseq. Since v0.1.2 the app bundles a static Android-compatible Termux PRoot (Termux itself is not required), which handles outer-filter SIGSYS and logs the trapped syscall number. Disabling rseq alone was insufficient. See ADR-010 andpython3 tools/test_proot_seccomp.py. Rebuild all bundled native binaries withpython3 tools/build_proot.py. For SIGKILL (9), Android's child-process restrictions, memory pressure or aggressive battery management may be responsible. Keep the app on screen and disable battery optimization for OpenCodePhone. On supported devices, Developer options → Disable child process restrictions may also help. - Where are the setup logs? The failure dialog shows the last log lines;
the full log is stored at
Android/data/io.github.oustery.opencodephone/ files/linuxenv/bootstrap.log.
Requirements: JDK 21, Android SDK (platform 36).
git clone https://github.com/oustery/opencodephone.git
cd opencodephone
./gradlew assembleDebug # per-ABI debug APKs
./gradlew test # unit testsOutput: app/build/outputs/apk/debug/app-<abi>-debug.apk.
Release signing (optional): provide ANDROID_KEYSTORE_FILE,
ANDROID_KEYSTORE_PASSWORD, ANDROID_KEY_ALIAS, ANDROID_KEY_PASSWORD
as environment variables.
app/ UI: screens, navigation, foreground service, DI wiring
core/model/ opencode DTOs + bootstrap model (pure Kotlin, lenient parsing)
core/opencode/ HTTP client for `opencode serve` (REST + SSE)
core/linuxenv/ proot bootstrap, rootfs download/verify/extract,
opencode installer, process/server runtime
core/settings/ encrypted settings (Android Keystore)
core/designsystem/ M3 Expressive theme, tokens, shared components
Version pins live in gradle/libs.versions.toml and
core/model/.../Pins.kt (Debian digests, OpenCode version).
- Provider API keys and the GitHub token are stored in
EncryptedSharedPreferences(Android Keystore); keys are injected into the selected opencode server on connect. A remote server receives those provider keys only with explicit endpoint/account-specific consent, so it must be trusted. Git uses a child-environment credential helper restricted to HTTPS github.com; stored remote/push URLs are sanitized when a project is accessed. Rotate old exposed tokens; removing a URL does not remove copies from backups/history. - The local server is bound to
127.0.0.1and requires an automatically generated 256-bit secret stored in encrypted settings, separate from remote credentials. The app and its health probe use the same stable local secret. - If secure settings cannot be opened, startup shows a retry screen without deleting data or falling back to plaintext. Old plaintext settings are not automatically read/imported/deleted; Settings warns if the legacy file exists. Retry may resolve a temporarily unavailable Keystore. Explicit confirmed reset deletes encrypted settings and their key, preserving projects/rootfs; lost keys cannot be recovered. Settings offers an explicit legacy import/cleanup action.
- Non-loopback remote hosts use HTTPS with certificate validation. Enter a bare hostname/IP in Settings and set the TLS listener's port (usually 443). Put a plain OpenCode V1 server behind a trusted HTTPS reverse proxy that also forwards SSE, or use an authenticated encrypted tunnel terminating on the phone's localhost. HTTP is allowed only for explicit loopback hosts, never as a fallback after a failed TLS connection. Redirects are not followed.
- Provider synchronization is endpoint/account-scoped. Only provider credentials previously managed by this app are deleted after local removal; independently configured server credentials are not removed. See the remediation tracker for regression coverage and remaining device-validation limits.
- No secrets are ever committed; CI release signing uses repository Secrets.
- armv7 devices: OpenCode has no 32-bit ARM build, so the local agent is unavailable there — terminal + remote-server mode still work (ADR-005).
- Terminal is pipe-based in v0.1: commands and output work, full-screen TUI programs (vim/htop) do not (ADR-006).
- Chat rendering is plain text + structured tool cards; rich markdown is planned.
- Bootstrap requires internet; offline install is future work.
- Markdown rendering & diff viewer in chat.
- JNI pty terminal (ADR-006 supersession).
- Session search, project-level
AGENTS.mdmanagement. - Per-APK Play publishing tracks.
Issues and PRs are welcome. Please keep changes aligned with the ADRs — before a major change, add a research note and a new ADR (the project's own development ritual).
MIT. OpenCode is developed by Anomaly — OpenCodePhone is an independent client and is not affiliated with Anomaly.
Запускает ИИ-агент OpenCode целиком на вашем Android-телефоне — в rootless-окружении Debian, с полностью нативным клиентом на Kotlin / Jetpack Compose (Material 3 Expressive).
- Полноценное окружение Debian (trixie) в приватном хранилище приложения через proot — без root и без Termux.
- OpenCode CLI (
opencode serve) устанавливается внутрь окружения при первом запуске; приложение общается с ним по HTTP API на localhost. - Нативный интерфейс: Чат (сессии, потоковые ответы, активность инструментов, запросы разрешений), Файлы (браузер рабочей папки, редактор, git clone/commit/push), Терминал (постоянный шелл в Debian), Настройки (провайдеры, модели, сервер, git).
- Каталог моделей OpenCode-first: чат показывает модели подключённого сервера opencode — стандартные и бесплатные модели доступны без своего API-ключа; собственные ключи провайдеров опциональны (шифруются на устройстве); есть режим удалённого сервера.
- Первый запуск скачивает Debian с проверкой SHA-256 и OpenCode CLI с
проверкой SRI — все версии зафиксированы (см.
Pins.ktи ADR-003).
Сборка: ./gradlew assembleDebug (JDK 21, Android SDK 36). Релизы создаются
автоматически: каждый push в main — workflow сам ставит теги, публикует
подписанные стабильный vX.Y.Z и dev-пререлиз vX.Y.Z-dev.N с APK (см. раздел
Releases). CI-артефакты (debug APK) — в разделе Actions. Подробности, решения и
ограничения — в англоязычной части README и в docs/.
Settings → App updates checks the latest stable GitHub Release on request.
It selects an exact per-ABI release APK, downloads to private cache (150 MiB
limit, cancellable), checks the published SHA-256 when available, and always
requires the same package/signing certificate and a higher Android version code.
Android's installer still asks for confirmation. If unknown-source permission is
needed, allow this app, return to Settings and press Install again. A cancelled
installer can be opened again. Debug APKs cannot update to differently signed
release APKs; signing-key rotation is intentionally not supported by this initial
updater. No background checks, silent installation or GitHub credentials are used.
Releases must keep the workflow's OpenCodePhone-vVERSION-ABI.apk names and bump
both the stable version and Android version code. A failed check/download can be
retried; no update replaces the currently installed APK without user consent.
Settings → Local server has Stop/Start as well as Restart. Stop terminates the local server and disables automatic restart for the current app process, including configuration changes, chat reconnects and environment reinstall. Start explicitly re-enables it. A new app process restores the existing autostart default. This does not delete the environment/projects or terminate terminal sessions. Remote servers are never stopped by these controls.
Chat lists only the connected opencode provider reported by the server, not the
full third-party /provider catalog. Available standard and free models come
from the live server: no model IDs or permanent free offerings are hardcoded.
A valid saved OpenCode selection is kept; otherwise a zero-input/output-cost
model is preferred, then the server's OpenCode default. Unknown prices are never
labelled free. Free public models need no user-entered provider key; OpenCode
still controls availability, usage limits and billing of other models. If OpenCode
is disabled/unavailable, the app reports that instead of silently sending prompts
to a third-party/default provider. Custom provider credentials in Settings are
retained for compatibility, but do not populate the primary chat catalog.
The M3 Expressive remediation tracker
links E-001–E-016 to implementation and remaining device checks. The shared
component gallery has light/dark, large-font and expanded previews. Native
Robolectric render/semantics probes live in core/designsystem/src/test and
export PNGs to build/reports/ui-renders in that module. They verify selected
color, action, disclosure and large-type invariants, not a complete full-screen
golden baseline. python3 tools/check_ui_policy.py rejects M2 imports and
screen-level hex colors/font sizes; it is required by the Gradle test lifecycle.