The proof
What ran, where
fx (vercel-labs/fx, native Zig) cross-compiled as a static binary and shipped inside a native Kotlin app. It ran full agent turns on an Android 12 x86_64 emulator. No Termux, no WebView, no Node.js.
Evidence, captured from the running app
fx ask --json --yolo returned rc=0 in about 1.3 seconds, and fx wrote
files/fx.out:
{
"output": "Tool round-trip complete: fx executed `uname -m` on-device and the architecture reported back was 'exit_code=0\n<stdout>\nx86_64\n</stdout>'.",
"exit_code": 0,
"model": "openai/gpt-5",
"steps": 1,
"tool_calls": [
{
"name": "terminal",
"status": "success",
"command": "uname -m",
"exit_code": 0,
"stdout_bytes": 7
}
]
}Three more artifacts back it up. files/result.json carries runtime
metadata with the full fx stdout and stderr. The session persisted under
files/home/.fx/sessions/. And the recovery machinery proved itself:
with the gateway unreachable, fx retried 10 times with backoff and paused
durably. The APK ships arm64-v8a + x86_64.
The ABI note
arm64-v8a is the primary artifact. The end-to-end run executed the x86_64 build because the emulator runs on an x86 host, and ARM images cannot run there. Both ABIs come from the same recipe.
The provider caveat
No model API credentials were available, so the model backend is a mock
gateway on the host, mirroring fx’s own e2e test fixture. fx already
ships loopback-only overrides for exactly this, so the app points
FX_GATEWAY_BASE_URL and FX_GATEWAY_CHAT_URL at 127.0.0.1:8099,
reached on-device through adb reverse. The gateway path, the SSE
transport, and the tool loop all ran for real.
Follow-up: native OpenRouter, proven on-device
The mock always had an expiry date. fx later gained a first-class
openrouter provider reusing its existing OpenAI Responses client. No
proxy, no adb reverse, direct HTTPS from the app. Verification climbed
a ladder: host binary against a Responses-SSE fake first, then the same
fake reached from the emulator, with wire captures showing plain Bearer
auth and the correct input and tools shape across the replay. 24 tools
advertised. What remains is a user-supplied key. Live inference needs
one, and none lives in the tree by policy.