Status: locked, post-spike; wrapper Docker verification complete. Last revision: 2026-05-21.
Working branch: main.
Project name: none yet. Branding emerges from building. Repo is Pi-Filling as a working title.
This document is the contract. Anything not in scope here is not v1, regardless of how appealing it sounds. When scope creep arrives, the answer is "see V1_SPEC.md, then re-open the question after v1 ships."
For how the pieces fit together, see ARCHITECTURE.md. For what comes next, see ROADMAP.md. For the spike's empirical results, see SPIKE_NOTES.md.
An Android-first AI coding agent that runs on the phone, not as a thin client to a server. The agent edits files, runs git operations, and uses Anthropic's API to do real software work. v1 is intentionally phone-only; multi-device "agent fabric" is v2+.
Two architectural bets:
- Kai already solved Android's hardest sandbox problems (Alpine + proot, foreground service, app-private storage, Compose entry point). v1 inherits Kai's bootstrap so we don't reinvent it.
memory.mdas a tracked file in a git-synced repo replaces vector stores, CRDTs, and sync servers in v1. Git is the sync protocol; conflicts are an LLM problem, not a distributed-systems problem.
Both bets survived the spike intact and are now baseline.
User opens the app on their Android phone, points it at a GitHub repository, types
"summarize the last three commits in CHANGELOG.md and push the result", and within a minute the change is in the remote.
The host-Alpine spike and the wrapper Docker suite have proven the agent half of this end-to-end (read → edit → commit, no push, against real Anthropic; plus hermetic wrapper protocol tests on Alpine/musl). The remaining work is the Android shell/proot integration, key storage, GitHub auth, UI/control layer, and the push step.
- Android only. No iOS in v1 (iOS has no proot path).
- Alpine Linux 3.21+ via termux/proot, bootstrapped using Kai's
build-proot.shandLinuxSandboxManagerpatterns. - Node ≥ 22 + git installed inside Alpine via
apk add nodejs npm git. - Foreground service based on Kai's
DaemonServicepattern (FGS typedataSync,START_STICKY,MainActivity.onStart()re-assertion). - File operations restricted to
/rootof the Alpine sandbox, with the user's chosen project directory mounted there.
@earendil-works/pi-agent-core— agent loop, event subscription,AbortSignalplumbing.@earendil-works/pi-ai— LLM client. Anthropic provider used; other provider SDKs are bundled but unused.@earendil-works/pi-coding-agent—createCodingTools(cwd)returns theread,write,edit,bashtools as plainAgentTool[]. We do not usecreateAgentSession()(itsSessionManager/AuthStorage/ModelRegistryare too opinionated for our wrapper).- These are the maintained successor to the
@mariozechner/*packages, which upstream deprecated; pinned to exact0.84.3. See the decision log and CHANGELOG. - All three are npm dependencies. We do not fork pi-mono.
- Single LLM provider for the shipping product: Anthropic. Initial model:
claude-haiku-4-5. Upgrade path to Sonnet/Opus via config; not a separate code path. - The wrapper additionally accepts
--provider ollama(OpenAI-compatible, Ollama Cloud). This is a development and testing affordance, not a v1 shipping target: it lets the agent loop be exercised end-to-end without Anthropic spend. Anthropic remains the default and the only provider Layer 1 wires up. Adding it did not fork the code path — provider selection is a data table, and the streaming call is the samestreamSimple. - API key supplied by user; stored in app-private storage (Keystore-backed where possible). Passed to the wrapper at run start via stdin or socket — never via persistent env vars or command args.
- Memory: sectioned markdown file (
memory.md) checked into the same git repo as the user's code. Read at start of each prompt, written during the run via theedittool the agent already has, committed alongside code changes. - Git operations (clone/pull/push/commit) via the
bashtool — no separate GitSync dependency. - Single git provider for v1: GitHub. HTTPS auth via personal access token or OAuth-device flow. SSH deferred (no safe place to store private keys yet).
- One UI surface ships in v1. Decision deferred to after the wrapper RPC is up. Candidates ranked by current preference: (1)
pi-coding-agent's TUI rendered in an Android terminal view; (2)pi-web-uiin a WebView; (3) Compose-native chat. Whichever ships first acceptably.
- Server-side companion agent (v2)
- Tailscale / GitHub-issue / chat-based async transport (v2)
agentskills.io/ SKILL.md skills loader (post-v1)- Sandcastle integration (v2)
- OB1 sync layer (v2 if ever;
memory.md+ git replaces it) - Hermes-style heartbeats / cron-driven autonomy (v2)
- Kai-style generative interactive UI screens (v2 at earliest)
- iOS port or thin client (v2+, may never)
- MarkText editor integration
- Multi-LLM via LMRouter (v2)
- Vector search / sqlite-vec derived index (flagged for v1.5)
- Persistent SAF directory grants — Kai doesn't ship this; we don't add it in v1
- Battery optimization whitelist prompt — Kai doesn't ship this; we don't add it in v1
createAgentSession()and pi-coding-agent'sSessionManager/AuthStorage/ModelRegistry. We usecreateCodingToolsonly.
| Component | Inherited? | Notes |
|---|---|---|
| Alpine minirootfs + termux/proot @ pinned commit | ✅ | RootfsDownloader.kt:17-27 (mirrors hardcoded), build-proot.sh (cross-builds for arm64-v8a, armeabi-v7a, x86_64) |
SandboxController interface (setup/execute/cancel/reset) |
✅ | Result envelope {success, stdout, stderr, exit_code, timed_out} |
DaemonService foreground service + MainActivity.onStart() re-assertion |
✅ | FGS type dataSync, START_STICKY. Survives Doze; may be silently killed by aggressive OEM ROMs |
build-proot.sh cross-compile pipeline |
✅ | Reuse as-is. NDK r29, Python 3, talloc 2.4.3 |
| SAF persistent directory binds | ❌ | Kai delegates to FileKit and does not call takePersistableUriPermission. We accept this for v1 |
| Battery optimization whitelist prompt | ❌ | Kai never requests REQUEST_IGNORE_BATTERY_OPTIMIZATIONS. We accept this for v1 |
| Notification listener / heartbeat scheduler | ❌ | Kai-internal, deeply tied to its ViewModel layer |
Tight couplings to lift when porting: Alpine version + 6 mirror URLs hardcoded in RootfsDownloader.kt:17-27; jniLibs/<abi>/ path baked into Gradle; FileProvider authority ${applicationId}.fileprovider. Plan to parameterize all three.
Known operational risks accepted: OEM-kill of the foreground service is silent (no UI warning) — recovery is automatic on next foreground; Android 14+ dataSync FGS has a 6-hour time limit, after which the user must reopen the app.
The host-Alpine spike answered every question the spec needed answered to lock Track A. Full record in SPIKE_NOTES.md; summary:
| Check | Result |
|---|---|
| Q1 — install + import on musl | ✅ Pure JS / WASM, zero native rebuilds. 121 MB of node_modules for Anthropic only |
Q2 — getApiKey injects per-call key into streamFn |
✅ Resolved key reaches options.apiKey exactly as supplied |
Q3 — agent.abort() cancels mock stream |
✅ stopReason: "aborted" in <100 ms |
Q4 — createCodingTools on musl |
✅ 4 tools (read, write, edit, bash); native musl clipboard prebuild picked correctly; +65 MB / +105 packages |
Q3-real — Anthropic SDK honors AbortSignal |
✅ 5 deltas streamed (419 chars), then aborted cleanly in 1569 ms |
E2E — agent reads README, edits, git commit |
✅ 4 turns, 3 tool calls (read → edit → bash), 5.2 s, stopReason: "stop" |
The "Track A vs Track B vs createAgentSession()" decision is closed in favor of Track A + createCodingTools. See SPIKE_NOTES.md "Verdict" section.
The Layer 3 wrapper now has repeatable tests at two levels:
| Check | Result |
|---|---|
npm test in node-wrapper/ |
✅ node --test reports 8/8 with no key; real-provider integration self-skips when ANTHROPIC_API_KEY is absent |
node-wrapper/test/smoke.mjs |
✅ Hermetic protocol coverage: wrapper_ready, hasMemory, state, bad params, unknown methods, idle abort, clean shutdown |
node-wrapper/Dockerfile |
✅ Builds on node:22-alpine, installs git, runs node --test by default |
| Wrapper Docker run | ✅ 8/8 passing inside Alpine/musl with no key; pass a key to additionally run real Anthropic integration |
spike-host-alpine Docker run |
✅ Q1 install/import, Q2 getApiKey/API-key plumbing, Q3 abort signal behavior all pass |
This verifies the wrapper is testable and portable to Alpine/musl. It does not yet prove the Android app can bootstrap proot, install Node/git, store keys, or drive the wrapper from a UI.
These are the practical details a v1 implementer needs to know that didn't fit in ARCHITECTURE.md:
pi-aieagerly loads every provider SDK at module init. 121 MB just for Anthropic. Worth flagging upstream after v1 ships if phone footprint matters; not a v1 blocker.pi-coding-agent'sbashtool's cwd ≠ thecwdarg passed tocreateCodingTools. In the e2e run, the agent prependedcd <repo>to its bash command defensively. Workaround: either run the wrapper withprocess.chdir(repoPath)before instantiatingAgent, or wrap the bash tool with one that injectscd. Filing upstream optional.Agent.shouldStopAfterTurnis onAgentLoopConfig, not on theAgentclass. No constructor option for it; setting it on the instance is dead code. To enforce a turn cap, useafterToolCallwithterminate: trueinstead.- Cold-start cost:
createCodingToolsimport takes ~800–1000 ms on x86_64 desktop. Realistic phone-in-proot estimate: 3–5 s. Not fatal; profile during port. - API key handling pattern:
getApiKey: () => process.env.ANTHROPIC_API_KEYis the spike pattern. v1 replaces this withgetApiKey: () => keyFromAndroidStorage, supplied via stdin or socket at run start.
The spike closed most of the previously-open ones. Remainder:
| Decision | Resolve when |
|---|---|
| UI surface (TUI / WebView / Compose-native) | After Android sandbox port runs the wrapper end-to-end |
| Resolved 2026-08-27: fine-grained PAT. See the decision log. | |
| Memory format evolution (sectioned markdown / JSONL / sqlite-derived) | When memory.md exceeds ~10k entries OR when v2 phone+server work begins, whichever comes first |
| Project name | When the product wedge becomes obvious |
All other prior questions are resolved (see decision log below).
| Resolved | Question | Choice |
|---|---|---|
| 2026-05-08 | Track A vs Track B for pi-mono integration | A (library import) |
| 2026-05-09 | pi-agent-core direct vs createAgentSession() |
pi-agent-core direct |
| 2026-05-09 | Roll our own tools vs use upstream | createCodingTools from pi-coding-agent |
| 2026-05-09 | Standalone gitsync dep vs shell-out git |
Shell out via the bash tool |
| 2026-05-09 | Inherit Kai's SAF persistence | No (Kai doesn't ship it; v1 doesn't add it) |
| 2026-05-09 | Inherit Kai's battery whitelist prompt | No (Kai doesn't ship it; v1 doesn't add it) |
| 2026-05-09 | License | MIT |
| 2026-06-02 | @mariozechner/* agent stack deprecated upstream |
Migrate to maintained @earendil-works/* (pinned 0.78.0, since moved to 0.84.3); re-verified Q1–Q3 + wrapper suite. See CHANGELOG. |
| 2026-08-27 | The host-capability channel could be ours or proposed to pi upstream; the voice extension could be shared with pi-termux-android-voice or vendored |
Vendor the extension here — its transport is being replaced and it will grow phone-specific behaviour Termux has no use for, so sharing would be a fork in disguise. Build the channel here too, but constrained so it could become a pi proposal later: namespaced host-neutral capability names, no Android specifics on the wire, transport isolated behind one module. pi ExtensionAPI has no host surface today, which is the whole gap. |
| 2026-08-27 | Mobile input is the real constraint, not compute; pi in Termux works but is unpleasant to type into | Voice input needs no work — any Android dictation keyboard types into the prompt box as normal text, the same conclusion pi-termux-android-voice reached. Voice output and every other phone-native capability need a host-capability channel (reverse RPC, Layer 3 → Layer 1), designed in ARCHITECTURE.md. Offline recognition via Vosk is the intended eventual direction but is not v1. Speaking aloud defaults off. |
| 2026-08-27 | 1.5 started, and the spec deferred "PAT vs OAuth device flow" to this point | Fine-grained PAT. OAuth device flow needs a registered GitHub OAuth App and a shipped client_id — real infrastructure for a pre-1.0 project — and its scopes are coarser than a fine-grained PAT, which can be limited to specific repositories. The token is stored encrypted, injected as GITHUB_TOKEN, and read by a git credential helper at use time so it never lands on the guest filesystem. OAuth can be added later without changing storage or the helper; only how the token is obtained would change. |
| 2026-08-26 | Verifying the live agent loop required real spend against Anthropic | Added --provider with an OpenAI-compatible ollama entry for development only; Anthropic stays the default and the v1 target. Verified end-to-end on Ollama Cloud: tool call + correct answer, 715 tokens. |
| 2026-08-26 | 0.78.x pinned vulnerable transitives via an upstream npm-shrinkwrap.json, unreachable by overrides |
Migrate to 0.84.3: getModel → getBuiltinModel (pi-ai/providers/all), and Agent now needs an explicit streamFn (pi-ai/compat.streamSimple). Production advisories 5 → 0; Q1–Q3 + 23/23 wrapper suite re-verified. |
| 2026-06-02 | API key via env var (spike expedient) | Keep env delivery at spawn but scrub from process.env immediately after capture so it can't reach the bash tool or pi-ai's env fallback; non-env handshake still the longer-term goal. |
These came up during brainstorming and are explicitly not v1, even when they sound appealing:
- Server-side companion agent
- Multi-device shared memory beyond what git gives for free
- Cross-project / global memory
- Vector recall
- Skill marketplace integration
- iOS thin client
- LMRouter integration for multi-provider
- Generative UI à la Kai
- Sandcastle integration
- Hermes heartbeat-style autonomy
- Background autonomy of any kind on the phone
When in doubt: if it's not on the acceptance test path, it's not v1.
- Spike record:
SPIKE_NOTES.md, reproducible viaspike-host-alpine/RUNBOOK.md. - Architecture:
ARCHITECTURE.md. - Roadmap:
ROADMAP.md. - Kai sandbox spec:
theamericanmaker/kaibranchclaude/setup-codecarto-pipeline-NamtZ,.codecarto/. - pi-mono skill docs + API:
theamericanmaker/pi-monobranchclaude/create-skill-docs-sTAlC,docs/andpackages/. - Hermes design context (v2-only):
theamericanmaker/hermes-agentbranchclaude/codecarto-hermes-analysis-abvQm,.codecarto/. - Original handoff:
HANDOFF.md(preserved historical context; superseded by this document on conflicts).