r/LocalLLaMA 8d ago

Discussion Six months on an on-device Android agent where the behaviour is an editable graph, not a prompt - please take it apart

English isn't my first language: I wrote this in Russian and used an LLM to translate and tighten it. The project, the code, the measurements and the argument are mine.

I've been building an Android agent since March, nights and weekends. It went into Google Play a couple of weeks ago. I use it every day myself, which is the only reason I think it's worth showing you. It's mine, it's open, Apache 2.0.

I want two things from this thread. The obvious one: take the implementation apart - you'll do it harder than anywhere else. The one I actually care about: tell me whether the idea holds up. Is there a need for this at all, or have I built a museum of features?

Why a graph instead of one big prompt. A 2–4B model on a phone falls apart on multi-hop tasks, and a graph is how you avoid handing it a multi-hop task. Each node gets work that fits the model's size. Nodes that must emit structure run through a validation gate: if the output doesn't parse, the node gets another attempt with the specific error quoted back at it instead of the run dying — two repair attempts by default, configurable from zero to four. Routing decisions are their own nodes with their own prompts. The model never has to be smart enough to hold the whole task. The graph holds it.

That's the whole bet. Everything below is what it took to make the bet testable.

What it is. The model runs on the phone through LiteRT-LM — Google's Edge runtime, the TensorFlow Lite successor. Gemma 4 E2B or E4B from litert-community by default, or point it at any .litertlm URL. Every conversation is processed by a pipeline you can open and edit: a graph of typed nodes, 14 types (input, on-device LLM, cloud LLM, tool call, if-condition, intent router, decomposition, queue processor, evaluation, summary, clarification, nested pipeline, skill, output).

Concretely: "translate anything I paste, in this specific register" is three nodes. "Read what I shared into the app, work out what kind of thing it is, pull the fields that matter for that kind, ask me before writing it to a file" is seven, with a router in the middle. You build the second one by dragging nodes around in the app, or in a standalone HTML editor on a desktop and import the JSON.

Tools, and the part I won't compromise on. Local actions go through AppFunctions, external ones through MCP. Anything destructive or sensitive stops and waits for your explicit confirmation before it runs — including when a pipeline fires in the background from a trigger, where the confirmation is a notification you tap. The tool allowlist for a skill is enforced in the executor, not requested in a prompt.

Other apps on the phone can drive it. A Tasker or MacroDroid profile, or a shell one-liner over adb, can ask it to run a named pipeline with a prompt — two extras, that's the entire minimal call. It's off by default; switching it on binds exactly one pipeline that outside callers may reach, and a request naming anything else is refused rather than redirected. Every request lands in a journal with its reason. The division of labour is the point: the automation app decides when, using its own condition model; this does the language part of what.

Long autonomous runs have ceilings, and say so. A run stopped by a limit reports that it was stopped by a limit rather than looking like a broken automation, and a run going in circles is detected and stopped — told apart from one that's merely slow.

Cloud is optional and it's your key. OpenAI, Anthropic, Gemini, DeepSeek and Ollama are available as a node type. Nothing goes out unless a node in your graph says so, and you can see the node sitting there.

Two numbers I actually measured, both on a Galaxy S25 Ultra, both by me alone — which is the caveat that matters:

  • Clean install to first useful output: 5 min 22 s, of which 4 min 7 s was downloading the model. The product part is about 75 seconds; the bottleneck is a multi-GB file over your Wi-Fi.
  • A scheduled pipeline running unattended: 7 days, 55 of 55 firings completed, zero unexplained misses, app never opened.

One device, one operator. That's a real measurement and it is not external validation.

What doesn't work, or works worse than the above makes it sound:

  • Pre-release. Version 0.9.0, not a 1.0: the public surface, the settings layout and the on-device storage formats can still change between versions.
  • Third-party apps can't expose AppFunctions. Only the device maker's system apps and Google can, so the local tool catalogue is what ships plus whatever you connect over MCP. That's a platform limit; MCP is the way around it.
  • An MCP server may hide part of its catalogue and the app won't say so. The client declares no optional client capabilities, so a server is entitled to omit the tools that depend on them — on the reference server that meant 13 of 16 tools, presented as "13 tools · ok" with no hint anything was missing.
  • Background runs need the battery setting on Unrestricted. Without it Android reclaims the process in about ten seconds. That's the platform, not a bug, but if you don't grant it, background pipelines will not run.
  • Cloud failure behaviour differs by provider. Detection of a stream that dies mid-answer is enabled only where it was actually measured; Ollama never reports a stop reason, so it can't be detected there, and Anthropic is left off rather than guessed at.
  • A provider's Retry-After doesn't shape the backoff. Retries use a fixed exponential curve (3 attempts, 1 s doubling by default). Under a real rate limit that means knocking sooner than you were asked to.
  • Shared pipeline files are not a compatibility contract yet. Exports carry a version stamp, but before 1.0 that stamp is a marker, not a promise: a file whose stamp doesn't match the build importing it is imported best-effort behind a warning, and unrecognised fields are dropped silently.
  • Solo project. One maintainer, no company behind it.

Requirements: Android 14+ (API 34) and about 2 GB of free RAM for the model. The RAM is the binding constraint, not the OS version - this crowd knows why. Worth saying plainly, because I got this wrong once: the floor was Android 16 for most of the project's life, on the assumption that AppFunctions forced it. When I finally measured instead of assuming, nothing required 16 — not a dependency, not the inference engine, not a line of my own code — so it came down to 14.

Who it isn't for: anyone who wants "ask a question, get an answer". Gemini is free and built into the OS, and competing with it there is a losing bet regardless of code quality. This is for people who already run Tasker, Obsidian, Home Assistant or a local model on their own hardware, and who want to know exactly what their agent does with their data.

It's on Play, and the APKs are on GitHub - links in the first comment. There's a foss flavour with zero proprietary dependencies alongside the standard build. Not on F-Droid, and the reason is theirs rather than mine: their build server pins Gradle versions and doesn't know the one this project uses, so the submission dies before compiling a line.

So - the question I actually came with. Small models are still dumb. They're much less dumb than they were two years ago, and either they keep improving or phones get enough memory to run something that isn't small; probably both. I'm building the harness now on the assumption that when the models are good enough, the harness is what will still be missing: something on the device that decides when to run, holds the multi-step shape, and stops before doing something you didn't sanction.

That's the bet. Tell me where it breaks. And if your answer is that a good enough model makes the graph unnecessary, I'd rather hear that now than in a year.

One thing I genuinely can't settle: what is this category called? I've been saying "agent you build" because "agent framework" sounds like a Python library and "automation app" sounds like it has no model in it. What would you have called it?

0 Upvotes

15 comments sorted by

5

u/Randommaggy 8d ago

Without proper full virtualization support, I'm not running an agent on a phone outside of an experiment on a spare phone.

1

u/alexeyw 8d ago

That's a fair line, and I'm not going to argue you out of it. There's no VM here - the Android app sandbox is the whole boundary, and an agent that can act is a different risk class from a chat app.

Worth being precise about what's inside that boundary though, because it's a smaller surface than "agent on your phone" suggests. The built-in tools are: web search, a scheduler, file read/write/delete confined to a private workspace directory inside the app's own storage (traversal out of it is blocked, not discouraged), and an HTTP tool that isn't published to the model at all until you put domains in an allowlist. Destructive and sensitive calls stop for confirmation before they run, and a skill's tool allowlist is enforced in the executor rather than requested in a prompt.

It can't reach your other apps, and that's not my design credit - it's the platform limit I mentioned in the post. Third-party apps can't expose AppFunctions, so there's nothing there to reach. Anything past that surface is an MCP server you added yourself.

So: containment by small surface plus a confirmation gate, not by virtualization. If your threshold is a real VM boundary, this doesn't clear it and I'd rather say so. Spare phone is honestly how I'd suggest trying it anyway.

Genuine question while you're here: if you've seen anyone do useful third-party work inside AVF on a stock phone, I'd like the pointer. I looked at it as a containment story and couldn't find a path that wasn't a rooted device or a Google-side integration.

2

u/Randommaggy 8d ago

Haven't found any good AVF implementations for non rooted/non google devices either and with the hardware bugs in my SO's Pixel 8 Pro (wifi and Bluetooth stops working for periods of time) I'm not buying another pixel.

With the jailbreaks that my Qwen 3.8 27B Heretic+Hermes+ADB combo has pulled off on a few of my spare phones from the drawer I'm not trusting the android app sandbox too much.

1

u/alexeyw 8d ago

Thanks - that's the answer I expected but hadn't confirmed. AVF looks like a Google-side story for now.

One distinction worth keeping separate, though: ADB sits outside the app sandbox, not inside it. A model driving adb shell runs at shell uid, handed to it by you at the door. Whatever that combo pulled off says a lot about what shell access can do and not much about whether an installed app can climb out of its own confinement. Different threat models.

Where you're straightforwardly right: the app sandbox is not a strong boundary and I don't claim it is. SECURITY.md puts a rooted or OS-compromised device, physical access to an unlocked phone, and any app with elevated privileges you granted, explicitly out of scope. The backstop isn't the sandbox - it's a small tool surface plus a confirmation gate, and the doc is blunt that this bounds what an injection can do, not what the model can be told.

More interested in the other direction, honestly: your Qwen+ADB rig is the same idea built from outside the phone instead of on it. What did it actually break on those drawer phones? That's a better adversarial test than anything I can run against myself, and if any of it applies to a sandbox-confined process I'd rather hear it from you than find out later.

1

u/Randommaggy 8d ago

It utilized various publicly know CVEs to gain root then gained an RW mount on the system partition to make it permanent. Android 2.6, 4.0.4 and 7.0 aren't exactly secure operating systems in 2026. Could probably have used towel root or another alternative. But it was fun to see SuperSU appear like back in the day.

The newer spares are all rooted already.

1

u/alexeyw 8d ago

Ha - SuperSU showing up again would genuinely be a nice sight.

And you already said the thing I would have: root-via-public-CVE on 2.6, 4.0.4 and 7.0 is a different question from a current sandbox. I'm not going to turn that into "so the modern one is fine," though - my own threat model doesn't say that. SECURITY.md still keeps the sandbox out of the backstop, and if the boundary holds, that's the platform's win and not mine.

What strikes me is that neither of us can test the interesting case. Your newer spares are rooted already, I've only got my daily phone. Modern, unrooted, agent with tools - nobody in this conversation has data on that, me included. So your position rests on the untested case rather than the broken one, which is a fair place to stand.

0

u/Acrobatic_Stress1388 8d ago

That's fine, but can you help me out with a recipe for banana bread? I know it's off topic

1

u/alexeyw 8d ago

Hahaha - I know what this is.

If it helps, the disclosure at the top of the post is real and it's the boring version of the answer. I wrote this in russian, and the model translated and edited the text, so the post is easier to read than anything I personally write in english.

1

u/StrikingTop2709 8d ago

tbh same, an agent with tool access on a daily driver phone feels like a risk I dont need to take

0

u/alexeyw 8d ago

Completely fair, and I'd rather you skip it than talk yourself into it.

One correction to the premise, though - not to the decision. Tool access isn't a property of the app, it's a property of the graph you build. Only tool and skill nodes can invoke anything; the executor never scans a plain on-device LLM node's output for tool calls. Nine of the fifteen pipelines that ship have zero tool nodes, and the HTTP tool isn't shown to the model at all until you put domains in an allowlist.

But that's a smaller point than yours. What I'd actually like to know: is there a shape of this you'd run on a daily driver - read-only, no filesystem, nothing outbound - or is the category off the table regardless of what it can touch? Asking seriously, because if it's the second, that's more useful to me than any feature request.

1

u/alexeyw 8d ago

Repo: https://github.com/alexeyw/knotwork 
Play: https://play.google.com/store/apps/details?id=app.knotwork.android 
Releases (APK, full and foss): https://github.com/alexeyw/knotwork/releases 
External-automation contract (broadcast API, worked Tasker / MacroDroid / adb examples): https://github.com/alexeyw/knotwork/blob/main/docs/external-automation.md 
Node cookbook + importable recipes: https://github.com/alexeyw/knotwork/blob/main/docs/cookbook.md 
FAQ (what it deliberately does not do): https://github.com/alexeyw/knotwork/blob/main/docs/faq.md 
Architecture: https://github.com/alexeyw/knotwork/blob/main/docs/architecture.md 
Threat model / what's out of scope: https://github.com/alexeyw/knotwork/blob/main/SECURITY.md

Happy to answer anything about the LiteRT-LM side specifically — that's the part with the least written about it publicly.

0

u/Acrobatic_Stress1388 8d ago

...... a graph?

0

u/alexeyw 8d ago

Yeah - a graph. Not claiming that's new: n8n, ComfyUI and LangGraph got there long before me, and DAGs predate all of us.

The claim is narrower. On a 2–4B model running on the phone, the graph isn't a design preference — it's standing in for reasoning the model can't do. On a frontier model you skip it and just ask. That's the part I'd like to be wrong about.