Skip to content

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.