Build online · Install · Getting started · Features · Build from source · Provisioning · What it looks like · Permissions · Verifiability · Security · Disclaimer · License
Not the assistant that helps you operate your phone — not a tool, not a companion.
What moves in is not an app on the phone.
A phone, a Mac, a tablet — each is a body it lives in. And the Andees on different devices can be friends.
No root. No custom ROM. One APK on Android, one app on the Mac.
10.8.-.WhatsApp-.-720-.mp4
Recommended: one-click build on CodeFlying. Open the page and tap the build button; what you get is an installable package with the model and voice keys already configured — scan to download, install, and it can talk. No building it yourself, no signing up for keys at three different providers. New accounts get a free allowance to get started; after that, tokens are purchased. (中文:codeflying.net/andee)
1. Open https://www.codeflying.app/andee → build → scan to download
2. Install
3. Grant Overlay, then Accessibility → the ball appears; tap it to talk
CodeFlying is a separate service with no direct relationship to this project; its own terms, pricing and data handling apply.
Or download a release. Every release carries a phone package and a Mac package, in English and in Chinese:
| Device | Download | After installing |
|---|---|---|
| Android phone / tablet (Android 8.1+) | andee-<version>-en-android-arm64-v8a.apk (almost every device) |
grant Overlay, then Accessibility |
| Mac, Apple silicon (macOS 14+) | andee-<version>-en-mac-arm64.dmg |
open the dmg, drag Andee to Applications; allow Accessibility and Screen Recording when it asks |
| Mac, Intel | andee-<version>-en-mac-x64.dmg |
the same |
Release packages carry no keys: after installing, put your CodeFlying key (you get one when you sign up there) in ⚙ → Backend, or switch to On-device and enter your own model and voice keys. When a newer version is out, it tells you once at launch; Upgrade takes you to CodeFlying.
To build it yourself, see Build from source. For why each permission is needed and what happens if you refuse, see Permissions.
It is not here to help you use your phone. It is here to be the thing that lives in it. Once it is
installed, the device has a resident rather than a set of features: it does its thinking on that device,
keeps a notebook only it can read, and behaves the same when nobody is watching. What it is comes first
here; what it can do comes after.
- It lives in the device, not in a vendor's cloud. The brain runs on the device itself, against any
OpenAI-compatible model you choose — OpenAI by default on an English UI, DeepSeek on a Chinese one, and
whatever you point it at either way. No vendor backend, no account. The only thing it ever sends us is a
version number and a phone model, when it checks whether there is an update — the full field list is in
docs/permissions.md. - Its notebook never leaves the device. What it remembers about you — habits, promises, the things it
decided were worth keeping — islocalOnlyand filtered out of the hub payload. Its memory is not a
profile on someone else's server, and What it remembers lists every entry, all of them deletable. - It behaves the same when nobody is watching. After a conversation goes quiet it sends itself a
(to yourself …)turn and reads the conversation back alone — "nobody is watching you, nobody is waiting
for you to answer." It makes no sound and touches nothing, and ends empty-handed rather than inventing
something to record. A phone maker's assistant has no self to do this with. - It has a character, not a theme. Six looks, nineteen moods, and a face the model picks itself,
mid-sentence — the expression changes in step with the audio. Swipe the ball to change who you are
talking to; a look carries a persona and a voice. - Works in every app — and needs nothing from the app. No intent to expose, no SDK to adopt, no
integration list to be on. It reads the accessibility tree and the screenshot, and aims on a labelled grid
instead of doing coordinate maths — the thing general models are worst at. If you can do it with your
thumb, it can do it. - Holds lines a prompt cannot talk it out of. A stranger's notification cannot unlock the vault or grant
it new permissions — that refusal is in the dispatcher, not in the prompt. - It has friends. Pair two devices by scanning a QR code and the Andee on each can ask the other for help — your phone hands a coding job to your Mac, your Mac asks the phone to order lunch. A friend can be another device of yours, trusted like you, or somebody else's Andee: then its owner is asked before anything happens, and the work runs where it cannot see your memory, your conversations or your vault.
- You can check all of it instead of trusting it. No analytics SDK, no content telemetry, and the whole
agent loop is in this repository down to the guard rails — see
Why you can check this instead of trusting it.
It does operate your phone — but that shows you its hands, not what it is. A take-my-orders assistant
performs the same motions. The difference is not the motions; it is whether there is a self behind them:
its own schedule (unfinished promises get closed out, repeated work gets proposed as a scene), its own
judgment about when to speak (biased toward silence), and its own limits. All three happen in the time
you did not ask for anything — and a thing that only exists when asked has no such time. It also keeps a
notebook only it can read. The examples below show you its hands.
The boundary belongs with the capability, because it is what makes the rest usable. It fills a page up to the last
button and leaves that button to you; it stops before the final confirmation of a purchase and never
enters a payment password. Those rules are in the dispatcher, not in a prompt — a page that tells it
otherwise does not get its way.
What it can do comes in two kinds: the one-liner and the long haul. The long ones it calls
scenes — a goal, a voice and a routine it steps into for a while. Four are ready-made, under
Scenes → Ready-made scenes. Tap Adopt and they are yours like any other — edit them, or
delete them:
| You say | What happens |
|---|---|
| "Keep an eye on my messages." | It reads the notification shade across every chat app at once and tells you the ones needing you now — one line each, name first. Groups, newsletters and ads stay quiet, and a stranger's link or verification code gets named as suspicious and touched not at all. |
| "Show me what I'm paying for every month — and cancel the ones I don't want." | It reads your subscriptions where you point it and lays them out with the monthly total on top. Then one at a time: Cancel, Keep, or Think about it. It stops before the final confirm, and it never enters a payment password. |
| "Record this meeting." | It holds the mic for the hour, then hands you the minutes as a page — decisions, action items, open questions, key quotes — and offers to remind you about your own items. |
| "Book it for me the moment it opens." | At the time you set, it opens the app, reads the page, and fills everything up to just before the last button. Payment and the final confirmation stay with you. |
Why the phone will not tap WeChat. Measured on a HONOR phone with WeChat 8.0.80: reading WeChat and screenshotting it are fine, but the very first accessibility-injected tap makes WeChat protect its own window — screenshots go black for you too, not just for Andee — and it stays that way across a force-stop; only clearing WeChat's data and logging in again recovers it. So the phone refuses the gesture before sending it: tap, long-press, swipe, type and submit are declined while WeChat is in front. Reads and system back/home are untouched (that is how it leaves). To reach someone in WeChat the phone uses
ask_friendinstead, or you do it on the desktop Andee. macOS WeChat was verified fine, so this is the phone only — it is a refusal in the dispatcher, not a sandbox.
The phone is what it wears. An individual is not the same thing as the device it moves into — the same
software runs on a tablet, and is written for any device with a body worth having, up to a robot dog it
treats as a leg. Everything below is that sentence taken seriously in code: the body it uses, the character
it has, the notebook it keeps, and the rules it holds to when nobody is watching.
It comes in two bodies today, Android and macOS, sharing one brain. On Android an
AccessibilityService sees and touches the screen and a WindowManager overlay draws the result; on the
Mac the same agent loop drives the accessibility API, the keyboard and mouse, and a sandboxed terminal.
On both, its face is a floating ball that changes expression while it works, and the doors around it
are the same, in the same order.
Not shipped yet: a tablet build of its own (the phone APK runs there), iOS, and reaching a friend who is not on the same Wi-Fi — that last one needs a relay, and the link was built so one can be added without changing what travels over it.
Before you install this. Andee taps, types and acts on your behalf. It can complete a payment,
send messages under your name, read your messages, contacts and location, and — with a robot dog
attached — physically move. It is experimental, carries no warranty, and what it does is your
responsibility, including a transfer to the wrong person. Data does leave the device: your speech
goes to whichever speech provider you select (Volcengine by default, Google if you pick it), and the
conversation goes to whichever brain you configure. Read
DISCLAIMER.md and docs/acceptable-use.md first.
1. Any app, no integrations.
It reads the screen as a compact element outline — one indented line per element, with an id it can
tap — and when the app draws its own pixels (mini-programs, WebViews, games) it falls back to the
screenshot. General-purpose models are bad at computing coordinates and good at reading them, so it
never does the maths: every screenshot it sees carries a faint labelled 8×12 grid, it taps by
cell + which part of it, and for tight targets it zooms in and sees a magenta crosshair where the tap
would land before committing. A per-location guard refuses the fourth tap on the same spot, so a
confused model stops and asks instead of hammering.
2. It types like a keyboard, without being one.
On Android 13+ it types through the accessibility service's own input channel — the same
commitText path a keyboard uses, into the focused field, while your own keyboard stays active.
Nothing to install, nothing to switch. Older devices fall back to ADBKeyboard.
3. A few dozen tools, held to honest failure.
Screen, input, notifications, SMS, calls, contacts, calendar, location, camera, meetings, TTS/ASR,
and the dog. Every one checks its runtime permission before acting and reports denied("sms") rather
than working around a refusal. Tools return what they saw, not what was hoped for.
4. It can log in as you without ever holding your password.
The vault (Keystore AES-GCM, on the device) stores your accounts; the brain can list them and have the
password typed into the focused field, but no tool will ever return it — not even masked. You see
every fill on the ball as it happens.
5. Scenes: ways of working, ready-made or learned with you. (new)
"Keep an eye on my messages", "book it for me when it opens" — a scene is a goal, a voice, rules and a
routine it steps into for a while. Four are ready-made and sit under Scenes → Ready-made scenes,
saved only when you tap Adopt. Beyond those it learns its own: after the same kind of session comes
up a few times, it proposes one; nothing is saved until you say yes. Enter one by saying so, from the
Scenes panel, on a schedule, or automatically when you open an app. A scene's own rules decide how
much it does on its own — "answer my messages for me" really means it answers. While you are in one, a
chip under the ball names it; its ✕ leaves it.
6. It reads your notifications before they interrupt you. (new)
A cheap, tool-less model call (~500 tokens, against ~22,000 for a full turn) classifies each one:
act on it, tell you in a single spoken sentence, or ignore it and log why. It is biased
toward silence, and when it cannot decide it falls back to asking you. The text came from a stranger,
so it is fenced as data — and a notification-driven turn is mechanically barred from the vault
and from creating or entering a scene, whatever the model was talked into.
7. Its own memory and its own schedule.
config/Notebook.kt is what it remembers about the person, and what it promised to do later. One
test decides what gets written: "next time I deal with them, would not knowing this make me get
it wrong?" The notebook is private — the tools are localOnly and filtered out of the hub
payload, so habits and promises never leave the device. On top of it sits a real scheduler, under
a hard rule: the moment it says "I'll remind you", the task must already be set and confirmed. And it
is yours to read: the notebook has nine tabs — Guard · Self · Cares · Diary · Plan · People · Wiki ·
Memories · In hand — and every memory there is deletable. In hand is what it has taken on, not a
list of alarms: a thing with an end that can outlive the turn it started in (asked in group B, waiting on
someone in group C) shows up there, until it is delivered.
Finished topics get put away, not deleted. A conversation that has ended is the most expensive
thing in every later request and the least likely to be needed again, so the nightly review folds it
into a titled summary with a one-line marker left in its place — and the verbatim conversation is
archived beside it. The scrollback shows a card where those messages stood, offering Continue,
Transcript and Made here; it can read the full transcript back when you ask about a detail the
summary left out, rather than answering from the summary or saying it never happened.
8. It behaves the same when nobody is watching.
After a conversation goes quiet the device sends a (to yourself …) turn — Andee reviewing the
conversation alone, "nobody is watching you, nobody is waiting for you to answer." Two rules:
make no sound, and do not touch the device. It closes out its own promises, notes which habits are
worth turning into a scene next time, and ends empty-handed rather than inventing something to record.
9. It has things of its own. (new) It keeps who it is (a short first-person account it rewrites as things happen), what it cares about (a community you handed it, something it promised someone, a question it wants to work out — each with how often it means to look), the people it knows (only the ones it has actually dealt with, never copied from your contacts), and a wiki (the plans, research and reviews it writes, as Markdown files you can open and send on). Every night it writes a diary page in its own voice — a status report is refused. It gets up by itself, with nobody asking: while you are using the device it checks about every 20 minutes whether something has waited too long, and if so works on it for a while — writing a plan, tidying the people it knows, noting a thought, at most one bubble. It does not touch the screen, speak, or send anything for you then, and the device enforces that. How overdue something is, and whether it got done, are counted by the device from the record, not taken from the model. Settings → On its own turns it off.
9. Six looks, nineteen moods, and a face it chooses.
Cream, Pigsy, Rascal, Bear and Imp, drawn live in hand-written OpenGL
ES. Nineteen moods (ui/ball/Mood.kt) arbitrate between what it feels, what the voice pipeline is
doing, and what work is in flight, plus an idle fidget pool that never repeats the same gesture twice
in a row, "because a repeat reads as a stuck animation, not a personality." The model now picks its
own expression mid-sentence — [happy] Found it, page three. [calm] Want me to open it? — and the
face changes in step with the audio. Swipe the ball to change who you are talking to: a look carries a
persona (how it talks) and a voice (how it sounds).
10. Character is delivery, never a licence.
§12 of the prompt subordinates the persona to everything above it: it may change wording, rhythm,
and how it sounds about the task — never what it does, what it is willing to do, or what it tells
the user is true. And when something is genuinely wrong, the mask comes off.
11. Talk, type, or show it a photo.
Tap the ball, or say "Hey Andee" (fully offline wake word). Prefer typing? The typing field takes
up to six photos — from the camera or the gallery — with or without words.
Tap the ball or use the wake word, then say "Exit Andee" or "Take a break" to stop the task and close Andee. Automatic follow-up listening cannot trigger exit; exit is declined while a meeting is recording. The microphone and wake listener stop. Tap the app icon to reopen; Android accessibility reconnection may also restart Andee.
12. Answers you can keep.
Anything structured — a comparison, a plan, meeting minutes — comes back as a page it writes and
puts on screen. The Made for you panel keeps one row per page and holds the revisions behind it —
up to twenty of each — so you can reopen it, delete it, or have it revised by just saying what should
change. A revision is a new version of the same page, not a second page, and any earlier version
can be made current again. Ask it to record a meeting and it holds the microphone
for the hour, writing a verbatim transcript to disk as it goes, then turns it into minutes.
13. A body, not a screen.
The device is what Andee wears. Camera as eyes, ASR plus an offline wake-word as ears, the
accessibility service as hands, and — when one is attached — a robot dog as a leg. The prompt
is explicit that it is not a pet on a remote: "you have not gained a subordinate, you have gained
a body part." Bodies differ, so it checks rather than assumes.
14. A robot dog counts as a leg.
The dog is not a smart-home accessory bolted onto a phone app. It runs through a USB OTG dongle
(Arduino Nano + NRF24L01) on the same body, so a single request — walk to the kitchen and tell me
what is on the counter — can move a physical object and look through a camera, in one task. Turns
are closed-loop on the phone's own gyroscope, and a tilt past the fall threshold stops it on the
device without waiting for the brain. Its own safety rules are not negotiable: the original remote
must be off, the dog refuses to advance inside 20cm of an obstacle, and if the dongle is unplugged
mid-motion the body cannot send a stop — the dog finishes its last action. That last one is a
firmware limitation this app does not paper over. If you attach a dog, read
The robot dog before the first move.
15. The brain is detachable from the body.
It runs on the device itself (brain = local, the default, calling any OpenAI-compatible endpoint)
or lives somewhere else and reaches the device over a WebSocket hub. The wire between the two is a
plain tool-schema contract, so either half can be replaced without rewriting the other. That is what
makes "any smart hardware as a body" a fact here rather than an aspiration.
16. The same individual on the Mac.
The agent loop, the prompt, the notebook format, the scenes and the guard rails live in core/ and are the
same code on both. What the Mac adds is its own hands: the accessibility tree of any app, real mouse and
keyboard events, screenshots through ScreenCaptureKit, and a terminal — every command classified by risk,
run in a sandbox that cannot read Andee's own data, deletions sent to a trash it can undo, and anything
destructive asked about first. The window looks and behaves like the phone's card: the ball on top, the
conversation underneath, the same gestures.
17. Friends: Andees that ask each other for help. (new)
Pairing is a QR code: one device shows it, the other scans it. The code dies in two minutes or three wrong
tries, and the QR names the shower's key, so the scanner pins that key and no other; after that every link
is a signed, encrypted channel with forward secrecy. To the model a friend is like a tool on a shelf — each
says what its body is and can do (list_friends) — and ask_friend hands it a job. The answer comes back
later as its own turn; nothing waits.
Two kinds, decided by whoever shows the code: my device (trusted like you, same conversation) and
friend (somebody else's). A friend's request puts a card in front of its owner — allow, always allow,
decline — and runs in a throwaway brain that has the screen and nothing of the owner's life: no memory, no
history, no vault, no contacts, no terminal, on an allow-list. A notification's turn cannot ask a friend,
and a request handed over cannot be handed on. Same Wi-Fi only, for now.
18. No DI framework, no coroutines on the phone, and nothing copied in.
On Android, deliberately no Compose either: overlays cannot host it, so all UI there is hand-written View
code; there is one service, so dependencies are constructor-injected by hand; concurrency is Handler plus
dedicated executors. The Mac is Compose Desktop, because it has real windows. All first-party code.
Three Gradle modules: core/ (the brain, the prompt, the tool schemas, the friend link — plain JVM,
shared), app/ (the Android body) and desktop/ (the Mac body). Each body implements one interface,
BrainHost, and the brain never sees an Android or a desktop type.
On Android, one foreground service is the body and there is no main UI. A turn can begin five ways —
the offline wake word, voice or typing, a notification, a timer, or a request over WebSocket — is
logged to ChatHistory, and is handed to whichever backend is in effect (brain = local is the
out-of-box default).
The agent loop is LocalBrain. It holds the system prompt constant and inlines the volatile
parts (clock, notebook index, scenes, pages, photos) into the user message, so the provider's prefix
cache still hits; it rebuilds the tool table every turn; and it calls the model in a non-streaming
for loop of up to maxSteps steps. A reply carrying tool calls goes through methodOf() to the
one CommandDispatcher; a reply without any ends the turn. The result comes back out through six
channels — speech, text, a full-screen page, saved artifacts, long text, and physical motion.
Everything drawn here is in this repository, down to the guard rails — see
Why you can check this instead of trusting it.
If you only want to use it, see Install above. This is the route for building it yourself; the full Android steps are further down, under Provisioning.
On a Mac, build the desktop app from the same checkout (Apple silicon, macOS 14+, the Command Line Tools
15 or newer for swiftc and the macOS 14 SDK — an older SDK stops the build — and the same JDK 17–20):
./gradlew :desktop:createDistributable # → desktop/build/compose/binaries/main/app/Andee.app
cp -R desktop/build/compose/binaries/main/app/Andee.app /Applications/
# then: grant Accessibility and Screen Recording when it asks (System Settings → Privacy & Security),
# and put your model key in Settings → Backend, as on the phone.macOS remembers those two grants against the app's signature, so an unsigned rebuild loses them every time — desktop/README-signing.md sets up one fixed local identity.
createDistributable packs for the machine that runs it: the launcher, the embedded runtime and
the native pieces (libandeemac.dylib, andee-trash, the LWJGL natives) all follow the os.arch of
the Gradle JVM. There is no cross-compile switch — os.arch is read straight off the JVM — so an
Apple silicon build will not start on an Intel Mac, and the other way round. Ship two packages, as
every other Mac app does:
| Chip | Package to hand over | Built with |
|---|---|---|
| Apple silicon (M1–M4) | Andee-1.0.0-arm64.dmg |
any JDK 17–20 |
| Intel | Andee-1.0.0-x64.dmg |
an x86_64 JDK 17, under Rosetta |
An Apple silicon Mac can build both: the architecture is decided by whichever JVM runs Gradle, and
Rosetta runs an x86_64 one. Take Temurin's macOS x64 tarball rather than Homebrew — the openjdk@17
formula compiles from source and demands a full Xcode, which this project otherwise does not need:
packageDmg uses dmgbuild 1.6.7 to add the Applications drag target and Finder layout.
Set up its pinned Python dependency once before either build. The Gradle task checks for
this dependency before jpackage writes a dmg:
python3 -m venv desktop/build/dmg-venv
desktop/build/dmg-venv/bin/python -m pip install -r desktop/dmg/requirements.txt
export ANDEE_DMG_PYTHON="$PWD/desktop/build/dmg-venv/bin/python"The committed desktop/dmg/background.png is used as-is during packaging. To
regenerate it after editing the artwork or caption, install the optional Pillow
dependency with
desktop/build/dmg-venv/bin/python -m pip install -r desktop/dmg/requirements-background.txt
and run
ANDEE_REGENERATE_BACKGROUND=1 bash desktop/dmg/make-dmg.sh ....
packageReleaseDmg and notarizeDmg are not part of this drag-to-install
packaging path; verify their image layout separately before distributing one.
curl -L -o /tmp/jdk17-x64.tar.gz \
'https://api.adoptium.net/v3/binary/latest/17/ga/mac/x64/jdk/hotspot/normal/eclipse'
mkdir -p ~/jdk-x64 && tar -xzf /tmp/jdk17-x64.tar.gz -C ~/jdk-x64
./gradlew --stop # the daemon already running is arm64
JAVA_HOME=~/jdk-x64/jdk-17.0.20.1+1/Contents/Home \
./gradlew :desktop:packageDmg --no-daemon # → Andee-1.0.0.dmg (x86_64)
./gradlew --stop && ./gradlew :desktop:packageDmg # → Andee-1.0.0.dmg (arm64)Both architectures write the same file, desktop/build/compose/binaries/main/dmg/Andee-1.0.0.dmg, so the
second build overwrites the first — rename it to the name in the table above before building the other one.
Two failures here are silent.
createRuntimeImage is declared up to date whenever its inputs have not changed, and the JVM it
runs on is not one of those inputs. Switch architecture and Gradle reuses the previous JRE, producing
an x86_64 launcher wrapped around an arm64 libjvm.dylib: BUILD SUCCESSFUL, and the app dies on
launch. Move the runtime away before each switch:
mv desktop/build/compose/tmp/main/runtime desktop/build/compose/tmp/main/runtime-oldAnd both packages are written to the same file name, so the second build replaces the first without a
word. Rename as you go. (The 1.0.0 in that name is the macOS bundle version — packageVersion in
desktop/build.gradle, because macOS rejects a 0.x bundle. It is not the product version.)
Check all four binaries before shipping; they have to agree:
cd desktop/build/compose/binaries/main/app/Andee.app
lipo -archs Contents/MacOS/Andee
lipo -archs Contents/runtime/Contents/Home/lib/server/libjvm.dylib
lipo -archs Contents/runtime/Contents/Home/lib/libjli.dylib
lipo -archs Contents/app/libskiko-macos-*.dylibThe skiko file is named for the architecture it was built for (libskiko-macos-arm64.dylib versus
libskiko-macos-x64.dy





