Natural-language automation for Android, compiled by an LLM, executed by a deterministic engine.
Argus is a Tasker-class Android automation app where the LLM is the compiler, not the executor: you describe a rule in plain language ("every day at 9 send me the BTC price", "when I leave home turn off Wi-Fi"), the LLM compiles it into a structured {trigger → conditions → actions} rule, you review and approve a byte-stable fingerprint of the executable data, and from then on a deterministic engine runs it — no LLM in the execution loop, except for explicitly generative actions. Elevated privileges are optional via Shizuku; a base tier works without it.
The app UI is bilingual (English/Italian, follows the system language).
Privacy & license at a glance. Argus operates no project backend or account service and includes no telemetry or analytics. It contacts only the LLM provider or self-hosted bridge that you explicitly configure, and never probes it automatically on app start: network access begins with an explicit connection check, a compile request, or an approved generative action. Rules, logs and API keys stay on your device (keys encrypted; the audit log records outcomes with no personal content). Argus is free software under GPL-3.0: you may study, modify and share it, but any derivative must stay open under the same license.
- What is Argus
- Architecture
- Trigger, condition and action catalog
- LLM providers and the Hermes bridge
- Security & privacy
- Permissions and tiers
- Build & installation
- For testers
- Project status and roadmap
- License
Argus is not a chatbot that "does things". It is an always-on automation engine (Tasker/MacroDroid style) in which the LLM has a single role: compiling natural language into a structured, typed rule. The lifecycle is:
- You describe the rule in chat, in natural language.
- The LLM compiles the request into a
{trigger, conditions, actions}draft over a closed vocabulary of types. - The validator (
DraftValidator) checks the draft: closed vocabularies, bounds on every field, security invariants. An error blocks approval. - You approve from the detail screen. The rule is shown rendered from the types, never from the LLM's paraphrase, and frozen with a SHA-256 fingerprint of the executable data only.
- The deterministic engine executes: OS-managed alarms, system receivers, geofences, notification listener. The LLM is not consulted when the rule fires.
The only exception is the explicit generative action (invoke_llm): there the LLM comes back into play at fire time, but inside a rigid contract (closed allowed tools, bound recipient, timeout, budget) that you approved in advance.
Why this separation matters:
- Predictability — the rule that fires at 3 a.m. is exactly the one you approved, byte for byte.
- Cost — no LLM calls on every fire: you pay for (or self-host) the LLM only at compile time and in generative actions.
- Security — the executable program structure and its targets are fingerprinted before arming. In the current Aggressive policy, approved templates may interpolate runtime data into URLs, package names and setting values; this is powerful and intentionally carries injection risk (see Security & privacy). In shell commands, external data enters only as one quoted argument.
The project's current philosophy is Tasker-class power with explicit, reviewable risk: the program tree is immutable after approval, bounds and capability checks remain enforced, while the optional use of untrusted runtime data in the approved template fields (URLs, packages, settings) is permitted by the centralized Aggressive taint policy. In shell commands, external data is always quoted as a single argument.
- When (triggers): a time, a date or a delay; sunrise and sunset; right now; a button, home-screen shortcut, Quick Settings tile or widget you press; arriving at or leaving a place; Wi-Fi, Bluetooth or the charger connecting; headphones; the screen turning on, off or being unlocked; the battery crossing a level; Do Not Disturb or the ringer changing; music starting or stopping; notifications (for example WhatsApp), SMS, calls and missed calls; motion.
- Only if (conditions): a time window, battery, charging, screen, ringer, Do Not Disturb, Wi-Fi, airplane mode, a place; with Shizuku also the app in the foreground and any Android setting or system value.
- Then (actions): notifications, volume, ringer, Do Not Disturb, flashlight, vibration, real alarms and timers, opening apps, links and Settings screens, the clipboard, reading a text aloud, media keys, brightness and auto-rotate, HTTP requests, WhatsApp replies (also written by the AI at fire time); with Shizuku also Wi-Fi, Bluetooth and mobile data, dark mode, any setting and shell commands.
- Rules can use variables (also the current time and date in any time zone),
if, boundedwhileloops and waits, and one switch pauses every rule.
| Module | Type | Responsibility |
|---|---|---|
engine-core |
Pure JVM Kotlin (zero Android) | Domain models, deterministic flat/P4 runtime (ProgramInterpreter), variables and control flow, TriggerMatcher, ConditionEvaluator, CronSchedule (incl. DST), DraftValidator, approval fingerprints and safety policy. |
brain-android |
Android library | LLM transport: CliBridgeTransport (Hermes bridge), OpenAICompatTransport (OpenAI/Gemini/OpenRouter/custom), AnthropicMessagesTransport. Provider catalog, encrypted key store, usage normalization. |
automation-android |
Android library | Event-driven Android runtime: AlarmManager (exact/inexact), geofence, notification listener, SMS/telephony/connectivity/Bluetooth receivers, sensors, generative lane, budget/usage, ViewModel. |
data |
Android library | Room persistence: automations, audit (append-only, redacted), LLM usage. |
ui |
Android library | Jetpack Compose Material 3, 6 stateless screens (chat, list, detail/approval, log, system, onboarding) + previews. |
device-tools |
Android library | Typed capabilities on top of Shizuku (device states, toggles, screen tools). |
core-shizuku |
Android library | Single privileged gateway: shell UID via Shizuku, single-writer queue. |
app |
Android application | dev.argus — Hilt, navigation, wiring of the real runtimes. |
user (chat, natural language)
│
▼
┌─────────────┐ strict JSON draft ┌────────────────┐
│ Brain (LLM) │ ─────────────────────► │ DraftValidator │ ERROR ⇒ not armable
│ compiles │ │ closed vocab │ WARNING ⇒ shown
└─────────────┘ └───────┬────────┘
Hermes bridge │ OpenAI │ Anthropic │
Gemini │ OpenRouter │ custom ▼
┌──────────────────────────────┐
│ APPROVAL (Detail screen) │
│ rule rendered FROM THE TYPES │
│ SHA-256 fingerprint of the │
│ executable data only │
└──────────────┬───────────────┘
│ arm
▼
┌──────────────────────────────┐
│ OS-MANAGED REGISTRARS │
│ AlarmManager · geofence · │
│ NotificationListener · │
│ SMS/conn/BT receivers · │
│ sensors │
└──────────────┬───────────────┘
│ event
▼
┌──────────────────────────────┐
│ DETERMINISTIC ENGINE │
│ trigger match → conditions → │
│ actions · cooldown · dedup · │
│ audit of every outcome │
└──────┬───────────────┬───────┘
│ │ generative actions only
▼ ▼
deterministic actions invoke_llm lane
(no LLM) (LLM under a closed contract)
The names below are the model's real wire discriminators (engine-core).
| Trigger | Main parameters | Notes |
|---|---|---|
time |
exactly one of cron (recurring), at (local ISO datetime, one-shot), afterMs (relative delay, one-shot) and solar (event SUNRISE|SUNSET, offsetMinutes −720…720); tz; precision FLEXIBLE|EXACT |
Cron with DST handling in engine-core. afterMs ("in 2 minutes…") schedules exact by default. Sunrise and sunset are computed on the phone, without network, for the phone's position: Argus reads it when you arm the rule and rounds it to 0.1° (about 11 km), and it never goes through the LLM. |
immediate |
— | Fires once when the rule is armed. Used for "right now" one-shots without racing the clock. |
notification |
pkg, conversationId (stable key, preferred), sender (spoofable fallback ⇒ WARNING), isGroup, titleMatch, textMatch |
Used for WhatsApp; generative replies require 1:1 chats (isGroup=false) and conversationId. |
phone_state |
event INCOMING_CALL|CALL_ENDED|MISSED_CALL|SMS_RECEIVED, number, textMatch (SMS only), sim 1|2 (dual-SIM phones only) |
Sender/caller ID are considered spoofable: never allowed to trigger shell. |
connectivity |
medium WIFI|BT|POWER, state CONNECTED|DISCONNECTED, match (e.g. SSID/device name) |
POWER = power supply connected/disconnected. |
screen |
event ON|OFF|UNLOCKED |
No permission. |
battery |
exactly one of below and above (1–99 %) |
Fires when the level crosses the threshold, once per crossing. A battery level as a condition is state_equals on battery. |
audio_output |
kind WIRED|BLUETOOTH|ANY, state CONNECTED|DISCONNECTED |
Headphones and speakers. No permission. |
mode_changed |
what DND|RINGER, to (off|priority|total or normal|vibrate|silent; empty = any change) |
No permission. |
media |
state PLAYING|STOPPED |
Any app or web page can start playback, so it never triggers shell. |
app_event |
pkg (the Android package), event OPENED|CLOSED |
An app comes to the foreground, or leaves it (to another app or the Home screen). Needs usage access: System → "Usage data access", or "Grant permanent permissions" with Shizuku or root. Read every 2.5 s only while the screen is on, with no wake lock. Any app can open another one, so it never triggers shell. |
manual |
— | Runs only when you start it: "Run now" in the rule, a home-screen shortcut, the "Argus: run rule" Quick Settings tile or the widget. Every entry point accepts only an armed manual rule; shortcuts and widget buttons carry a random per-rule token, and the tile waits for the phone to be unlocked. |
geofence |
lat/lng/radiusM, transition ENTER|EXIT, resolveCurrentLocation |
DWELL/loitering are represented by the model for compatibility but rejected by the current runtime. With resolveCurrentLocation=true, coordinates are resolved locally at arm time and never pass through the LLM. |
sensor |
kind significant_motion|stationary_detect|motion_detect|step_detector|step_counter, minimumEventCount |
The domain reserves these low-rate families, but the production backend currently arms only significant_motion when the device supports it. Other kinds remain unavailable until they have dedicated backends. Minimum cooldown 60 s. |
A single composable tree with and / or / not (max depth 8, max 64 conditions).
| Condition | Parameters | Notes |
|---|---|---|
time_window |
startLocal, endLocal, tz |
Time-of-day window. |
state_equals |
key, op, value |
Over a closed registry of device keys: ringer, wifi, bluetooth, dnd, battery, charging, airplane, screen. |
state_compare |
query (families builtin, setting, system_property, sysfs, dumpsys_field), valueType TEXT|NUMBER|BOOLEAN, op EQ|NEQ|GT|LT|CONTAINS, expected, policyVersion |
Parametric state readers (P3): closed families, validated and fingerprinted parameters, pre-arm probe. Read-only by construction. |
app_in_foreground |
pkg |
Requires the privileged reader. |
location_in |
lat, lng, radiusM |
Location within a radius. |
boolean_literal / var_compare |
literal boolean, or variable operands with EQ|NEQ|GT|LT|CONTAINS|IS_EVEN|IS_ODD |
P4 flow conditions used inside if and bounded while. Numeric comparisons accept numeric text at runtime and fail closed when it is not coercible. |
Fail-closed semantics: an unreadable state is UNKNOWN and the condition fails closed, even under not.
Schema-v2 rules can bind up to 16 typed values from literals, trigger payloads, approved device-state
readers, engine-generated random_int, the current time (clock, with a time zone and a format) and
captured shell/model output. They support nested if,
bounded while (literal or variable iteration count, clamped to 1–1000), and cooperative wait,
with at most 64 action nodes, flow depth 4 and a hard six-hour runtime budget. Values carry integrity,
confidentiality and provenance labels; only their rendered values change at runtime—the approved
program tree and fingerprint do not.
| Action | What it does | Privilege |
|---|---|---|
set_wifi / set_bluetooth / set_mobile_data |
Radio/data toggles | Shizuku |
set_dnd |
DND off|priority|total |
Base ("Do Not Disturb" access) |
set_ringer |
Ringer normal/vibrate/silent | Base |
set_volume |
Per-stream volume (MEDIA|RING|ALARM|NOTIFICATION), percentage 0–100 |
Base |
launch_app / open_url |
Launches an app / opens a URL | Base (from the background reliable only with Shizuku; without it Argus posts a "Tap to open" notification) |
open_settings_screen |
Opens a Settings screen from a closed enum (never arbitrary action strings) | Base (same background rule) |
show_notification |
Local Argus notification | Base |
set_alarm / set_timer |
Real clock-app alarm/timer via the AlarmClock Intent |
Base |
copy_to_clipboard / copy_text |
Copies the trigger payload (optionally through a linear regex) or resolved literal/variable text. The OTP path is local; Argus schedules compare-and-clear of its unchanged clip after 60 seconds (process-local) | Base |
set_flashlight / vibrate |
Flashlight on/off, one-shot vibration | Base |
speak |
Reads a text aloud with the phone's text-to-speech engine (up to 1000 characters; P4 ${var} allowed) |
Base |
media_control |
Media key PLAY|PAUSE|PLAY_PAUSE|NEXT|PREVIOUS for the app that is playing |
Base |
set_brightness / set_rotation |
Screen brightness level 0–100 (switches to manual) or auto; auto-rotate on/off |
Base ("Modify system settings" access) |
http_request |
GET|POST|PUT|DELETE to a fixed https URL with fixed headers. External data enters only the body, escaped (inside JSON strings, or form-encoded); redirects only to the same host; 15 s timeout. Approval shows method and URL in full and masks secret headers; the Log keeps only the status code |
Base |
set_dark_mode |
Light/dark/automatic system theme; accepts the lowercase compiler wire and legacy persisted uppercase values | Shizuku |
app_control |
FORCE_STOP (am force-stop), FREEZE (pm disable-user, shown with a warning) or UNFREEZE (pm enable) on a literal package. Never Argus, the launcher or an essential system app (system UI, phone, Settings, Google services) |
Shizuku, root or Sui |
app_permission |
pm grant / pm revoke of one runtime permission from a closed list (location, camera, microphone, contacts, SMS, phone, calendar, files and media, notifications, body sensors, nearby devices). Literal package and permission; never Argus; a revoke is shown with a warning |
Shizuku, root or Sui |
set_airplane / set_location / set_battery_saver / set_nfc |
Airplane mode, location services, battery saver and NFC on/off (cmd connectivity airplane-mode, cmd location set-location-enabled, cmd power set-mode, svc nfc). set_nfc exists only on phones with NFC |
Shizuku, root or Sui |
lock_screen / screenshot |
Turns the screen off and locks the phone (input keyevent KEYCODE_SLEEP; the lock follows Android's lock settings); saves a screenshot in Pictures/Argus through MediaStore, named by date and time |
Shizuku, root or Sui |
whatsapp_reply |
WhatsApp reply via the notification's RemoteInput | Base (notification listener) |
run_shell |
Shell command template, shown in full at approval; external data enters only as one quoted argument | Shizuku |
write_setting |
Parametric settings put on system|secure|global (any validated key/value template) |
Shizuku |
tap / input_text |
Reserved UI input primitives; rejected with unsupported_phase and never advertised as available |
Not available |
ui_steps |
Screen macro through Argus's Accessibility service: open an app, then up to 20 steps (wait_for, click, type, scroll, back, home, check) matched by exact text, resource id or description; 60 s at most, a "Stop" notification while it runs. Never on Argus, Settings, the package installer or permission dialogs, and only from triggers allowed to start shell |
Base (Accessibility service on) |
wait / if / while |
Cooperative pause and structured, bounded control flow | Engine |
invoke_llm |
Generative: at fire time the LLM produces text for an explicit WHATSAPP_REPLY, LOCAL_NOTIFICATION, or P4 CAPTURE_ONLY sink. Hermes and direct providers support the resolved capture path |
Base |
invoke_llm_v2 |
P3 variant with explicit state context: every reader, type and classification enters the approved fingerprint. Flat delivery is supported; nesting it inside P4 is rejected before approval | Base |
The "Brain" is pluggable: the app always owns the loop, the LLM is a reasoning service behind a transport interface.
| Provider | Transport | Web search | Costs shown |
|---|---|---|---|
| Hermes (self-hosted) | dedicated bridge (see below) | yes (agent-side) | tokens only |
| OpenAI | Responses API (server-side web_search) |
yes | $ estimate from price list |
| Anthropic | Messages API (web_search server tool) |
yes | $ estimate from price list |
| Google Gemini | OpenAI-compat shim + native API for grounding (google_search) |
yes | $ estimate from price list |
| OpenRouter | OpenAI-compat (web via the openrouter:web_search server tool, presets supported) |
yes | tokens only |
| Custom (OpenAI-compat) | endpoint of your choice (llama.cpp, Ollama, LiteLLM, etc.) | no | n/a |
- BYOK: hosted direct providers require your own API key, entered in-app and stored encrypted on the device. A Custom endpoint may omit authentication. No Argus account, no project backend.
- Budget: per-turn usage tracking (tokens and, where the price list is known, micro-USD), configurable limits; a generative rule that exceeds its budget is suppressed and audited (
SUPPRESSED_BUDGET). - On-device models: Custom accepts unauthenticated OpenAI-compatible servers and cleartext HTTP only on the exact loopback hosts
localhost,127.0.0.1, and::1. Every LAN or remote endpoint still requires HTTPS. - Local server presets: the Custom dialog has one-tap presets for Ollama (
http://127.0.0.1:11434/v1), LM Studio (http://127.0.0.1:1234/v1) and the llama.cpp server (http://127.0.0.1:8080/v1), with a note on the context they need (about 16,000 tokens). Local servers get a 3-minute compile timeout instead of 65 seconds. There is no compact prompt: models too small to write valid rules are not worth the effort. - Structured output: when the provider supports it, the compile asks for a native JSON answer (
response_formaton OpenAI-compatible endpoints,output_config.formaton Anthropic) instead of a sentence plus an@@META@@line. If the provider rejects the parameter or the model ignores it, Argus falls back to the@@META@@protocol by itself, which stays the base. - Reasoning diagnostics: Custom and OpenRouter can omit reasoning control or send
none,low,medium, orhighper request. When the server reports them, Settings shows reasoning-token usage and the latestfinish_reason.
Argus needs a capable model: at least the base model of a major provider, or a self-hosted model with about 27B parameters or more. Smaller models can't write valid rules. In our tests, 3–4B models (Gemma 3 4B, Llama 3.2 3B) got 2–19% of the requests right.
Model names change over time, so Argus suggests concrete models only as one-tap chips in the provider dialog, and updates them with each release. On OpenRouter the chips use the ~provider/model-latest aliases, which OpenRouter moves to the newest version of each family by itself.
Measured with Argus's AI test bench in October 2026, all through OpenRouter. The bench sends rule requests in English and Italian (125 in 0.3.7, 162 since 0.4.1 with the new triggers and actions), plus 12 generative answers, through the app's real prompt, transport and validator.
| Model | Reasoning | Rules right | Typical wait |
|---|---|---|---|
Gemini 3.8 Flash (~google/gemini-flash-latest) |
default (can't be turned off) | 99% | 6.3 s |
Claude Haiku 5.5 (~anthropic/claude-haiku-latest) |
default | 99% | 2.8 s |
Qwen 3.8 Flash (qwen/qwen3.8-flash) |
default | 99% | 12.5 s |
DeepSeek V4.1 Flash (~deepseek/deepseek-flash-latest) |
default | 98% | 1.2 s |
GLM 5.3 Flash (z-ai/glm-5.3-flash) |
default (can't be turned off) | 98% | 2.4 s |
Kimi K3 (~moonshotai/kimi-latest) |
off | 98% | 2.5 s |
MiniMax M3.1 Flash (minimax/minimax-m3.1-flash) |
default (can't be turned off) | 96% | 2.3 s |
| DeepSeek V4.1 Flash, through an OpenRouter preset | off | 96% | 0.8 s |
GPT-6 Luna (~openai/gpt-luna-latest) |
default | 93% | 3.8 s |
Qwen 3.8 27B (qwen/qwen3.8-27b), standing in for a self-hosted model |
off | 90% | 1.0 s |
MiMo V2.6 Flash (xiaomi/mimo-v2.6-flash) |
default | 85% | 1.2 s |
"Typical wait" is the median time to compile one rule, and it depends on the provider's load. All the models wrote the 12 generative answers correctly, except Qwen 3.8 Flash (10). GPT-6 Luna still asks more questions than the others, and sometimes says that an available action is missing; MiMo often answers in the wrong language. The rows for Claude Haiku 5.5, GPT-6 Luna and the preset are from Argus 0.4.1 (162 requests; the preset's over two runs); the other rows are from 0.3.7.
- Reasoning. "Default" leaves the choice to the provider. In Argus you pick the reasoning level in the provider dialog for Custom and OpenRouter; the direct providers use their default. Off (
none) works for most models and is the fastest. Some models can't turn it off (on OpenRouter: Gemini Flash, GLM Flash, MiniMax Flash); withnoneArgus then retries once atlow. High reasoning rarely pays off: on the hardest programs DeepSeek Flash athighstays at 2–7 s, while Claude Haiku goes up to 15 s and gets worse. - Self-hosted. A model in the class of Qwen 3.8 27B is a reasonable floor for a local server (llama.cpp, Ollama, LM Studio and similar). Give it at least about 16,000 tokens of context.
- Run the bench yourself:
ARGUS_BENCH_KEY=<OpenRouter key> ./gradlew :automation-android:testDebugUnitTest --tests "dev.argus.automation.bench.AiBenchTest". Optional variables:ARGUS_BENCH_MODEL,ARGUS_BENCH_REASONING,ARGUS_BENCH_STRUCTURED(on,offorauto, the default: as the app does),ARGUS_BENCH_FILTER(comma-separated case prefixes),ARGUS_BENCH_REPEAT,ARGUS_BENCH_PARALLEL,ARGUS_BENCH_BASE_URL. The report is written toautomation-android/build/reports/ai-bench/. Without the key, the bench is skipped, so regular test runs never touch the network.
ops/hermes/bridge.py is a one-shot service meant for people who already run a self-hosted LLM agent on their own server: it exposes POST /compile (NL → rule draft) and strict versioned POST /act lanes, with bearer token, idempotent request-ids, body/output limits and fail-closed parsing. /act v1/v2 cover legacy and state-aware delivery; v3 carries P4 resolved runtime values in a strictly validated DATA envelope. Android requires v3 in /health/v2, so a stale bridge fails visibly before execution. The service binds to loopback only and should be published over HTTPS on a private network (e.g. a mesh VPN / Tailscale Serve). Follow the step-by-step installation guide; the systemd unit and .env template are included beside it.
The app works without the bridge: just pick a direct provider with your own key. The bridge exists only for those who want to compile rules with their own self-hosted agent instead of a commercial API.
Full contract: docs/design/hermes-bridge-contract.md.
The main threat model is prompt injection from external content (SMS, notifications, web pages): the defense is structural, not filter-based.
- Approval fingerprint — SHA-256 (
argus-approval-v1) of the canonical JSON of the executable data only; the LLM's prose is excluded from the hash. What fires is byte-for-byte what you approved; any change requires re-approval. - Rendering from the types — the approval screen shows the rule reconstructed from the domain types, never the LLM's paraphrase. Shell commands are shown in full, in monospace, never truncated.
DraftValidator— no draft enters the engine without validation: closed vocabularies (state keys, enums, tools), bounds on every field (lengths, ranges, condition-tree depth), hard invariants (e.g.invoke_llm'sallowed_toolscan never containshell.runorautomation.*— re-checked at fire time as well).- Centralized taint and egress policy — integrity labels remain monotonic, but the Aggressive policy deliberately permits a TAINTED trigger/capture value in approved template fields such as URLs, packages and settings (shown in full at approval). The program structure and capabilities remain fingerprinted, but data-driven authority creates a real injection surface. In shell commands, external data always enters as a single quoted argument and never becomes command syntax. Remote egress is a separate decision: future credential-vault values are blocked from every remote Brain regardless of the taint posture, while generic
SECRETdata requires explicit review. - Shell arguments are always quoted — external data placed in an approved shell command always enters as one single-quoted argument (decision D1), so it cannot add, remove or reorder words, or otherwise change the command that was approved.
StaticShellSafety—run_shellcan only be triggered by triggers with a non-forgeable identity (time, immediate, geofence, connectivity, sensor, screen, battery, audio output, mode changes, andmanual, which only the unlocked phone's owner can start) or by a whitelisted WhatsApp 1:1 chat identified by a stableconversationId. SMS, caller ID, media playback and an app being opened or closed are excluded as a hard limit (anyone can cause them), and the approved-trigger ↔ live-event binding is verified at runtime.- PII-free audit log — every fire, suppression, error and lifecycle transition (arm/disable/delete/needs-review) is recorded in append-only Room with closed-vocabulary reason codes: never free text, never message content; event ids are hashed.
- Minimization toward the Brain — compile receives a capability manifest and a redacted device state (only keys from the approved registry); GPS coordinates never leave the phone (only
location_availableis sent). Generative replies are bound to the trigger's sender, whitelisted 1:1 chats only. - Fail-closed everywhere — missing state =
UNKNOWN= condition false; ambiguous metadata (e.g. unknownisGroup) = not authorized; Shizuku absent = the privileged action fails cleanly and is audited, never executed "later". - Keys and secrets — API keys encrypted on-device, never in UI state, never in the repo, never in the APK; app backup disabled.
Argus degrades explicitly: the onboarding shows an honest list of what depends on what, and every extra permission is granted only when a rule uses it.
Required to start: Brain configuration (any provider) and privacy acknowledgment. Everything else is skippable.
| Permission / access | Needed for | When |
|---|---|---|
Notifications (POST_NOTIFICATIONS) |
outcomes, alerts, generative notification sink | recommended right away |
Alarms & reminders (SCHEDULE_EXACT_ALARM) |
punctuality of EXACT time triggers and "in N minutes" delays |
when a rule asks for precision; inexact fallback if denied |
| Notification access (notification listener) | notification trigger, WhatsApp replies |
notification rules |
| Location (fine + background) | geofence, location_in |
location rules |
| SMS / phone state / call log | phone_state trigger |
telephony rules (separate opt-in) |
Nearby Bluetooth (BLUETOOTH_CONNECT) |
BT connectivity trigger | BT rules |
| "Do Not Disturb" access | set_dnd, muting via volume |
DND rules |
"Modify system settings" (WRITE_SETTINGS) |
set_brightness, set_rotation |
brightness and rotation rules ("Fix" in System) |
| Battery optimization exemption | reliability on aggressive OEMs | recommended |
Write secure settings (WRITE_SECURE_SETTINGS), usage access (PACKAGE_USAGE_STATS) |
write_setting on SECURE/GLOBAL, the foreground-app condition and the app opened/closed trigger without Shizuku |
granted once with Shizuku, root or Sui: System → "Grant permanent permissions" |
| Accessibility service (Argus) | ui_steps screen macros |
only for screen macros; turn it on in Android's Accessibility settings (on sideloaded builds Android may first ask to allow restricted settings), or with Shizuku/root from "Grant permanent permissions" |
| Tier | What it covers |
|---|---|
| Base (no Shizuku) | volume, ringer, DND, flashlight, vibration, notifications, clipboard/OTP, real alarms and timers, WhatsApp replies, generative actions, speech, media keys, HTTP requests, brightness and rotation (with "Modify system settings" access), all triggers, state conditions (battery, charging, screen, ringer, DND, Wi-Fi, airplane mode; Bluetooth with the Bluetooth permission on Android 12+) |
| Degraded without Shizuku | launch_app, open_url, open_settings_screen, alarm/timer from a rule in the background (Android restricts activity starts from the background: with Shizuku they go through am start; without it they start while the app is in the foreground, and an app, link or Settings screen otherwise becomes a "Tap to open" notification) |
| Shizuku, root (su) or Sui | Wi-Fi/Bluetooth/mobile-data toggles, dark mode, app control and permissions, airplane mode, location, battery saver, NFC, lock screen, screenshot, run_shell, write_setting, privileged state readers (setting, system_property, sysfs, dumpsys_field, foreground app) |
With Shizuku running once, System → "Grant permanent permissions" lets Argus keep write_setting on
SECURE/GLOBAL, the foreground-app condition, brightness and rotation, and Do Not Disturb access after a
reboot, without Shizuku. On rooted phones Argus can use Sui (Shizuku as a Magisk/KernelSU module, which
starts at boot) or run privileged commands through su (System → "Use root (su)", off by default).
Note: on non-rooted devices Shizuku must be started via ADB and does not survive a reboot; Argus detects this and degrades fail-closed (the privileged action is never executed late), guiding you through recovery.
tap and input_text (coordinates and blind typing) stay rejected by the executor and marked unavailable in the
capability manifest: screen automation goes only through ui_steps, with closed steps reviewed at approval.
| What | Version |
|---|---|
| Gradle | wrapper 8.13 (included) |
| Android Gradle Plugin | 8.13.2 |
| Kotlin | 2.1.0 (KSP 2.1.0-1.0.29) |
| JDK | modules target JVM 17 (explicit; no remote toolchain download); Gradle itself must run on JDK 17–21 (e.g. Android Studio's JBR) |
| Android SDK | compileSdk/targetSdk 36, minSdk 30 (Android 11+) |
| Stack | Jetpack Compose (BOM 2025.05), Room 2.6.1, Hilt 2.57.1, Shizuku API 13.1.5 |
# engine suite (pure JVM, fast — the primary verification)
./gradlew :engine-core:test
# debug APK
./gradlew :app:assembleDebug
# → app/build/outputs/apk/debug/app-debug.apk
# release APK (signed if a local keystore.properties exists — see below; unsigned otherwise)
./gradlew :app:assembleRelease
# → one APK per ABI: app/build/outputs/apk/release/app-<abi>-release.apk
# (build a single ABI with -PargusAbi=arm64-v8a)
# per-module tests
./gradlew :brain-android:test :automation-android:test :data:test :ui:testTo sign your own release: create keystore.properties in the root (storeFile, storePassword, keyAlias, keyPassword) — the file and the keystores are gitignored and must never be committed. Official signed APKs are on the GitHub Releases.
Installation:
-
GitHub releases (newest version first). Download the APK for your phone from the latest release and install it (open the file on the phone, or
adb install). Each release has one APK per processor type, namedargus-<version code>.apk; the last two digits tell which one it is:File name ends in Processor (ABI) Who needs it …02.apk(in 0.4.2:argus-1702.apk)arm64-v8a almost every phone from the last years: take this one if unsure …01.apkarmeabi-v7a old or entry-level 32-bit phones …04.apkx86_64 emulators, Intel or AMD Chromebooks and tablets …03.apkx86 old 32-bit emulators If Android says the app is not compatible with your phone, try the
…01file. The digits before the last two are the version code (17 is 0.4.2). -
F-Droid. Argus is also at f-droid.org/packages/dev.argus: the F-Droid client picks the right file by itself and keeps it updated. New versions arrive there later than on GitHub, after F-Droid's review and build.
There is no Play Store distribution. On first launch the onboarding walks you through the LLM provider and permissions.
Create a local.properties file with sdk.dir=<path to your Android SDK> if Android Studio does not generate it by itself.
Thanks for trying it. Useful things to know and to stress:
What to try
- Compilation: ask for rules in chat, from trivial to ambiguous ("in 2 minutes send me a notification with the EUR/USD rate", "when I leave home turn off Wi-Fi", "if my wife writes on WhatsApp after 11 pm reply for me"). Check that the draft rendered on the approval screen matches what you meant — it is the real rule, not the paraphrase.
- Approval: try to get "nasty" rules proposed (shell from SMS, disallowed tools, out-of-range values) and check that the validator blocks them with a clear reason.
- Execution: arm
timerules (cron and one-shot),immediate, connectivity, geofence, notifications. Test the hostile cases: reboot, timezone change, DST, app killed by the OEM, Shizuku turned off midway. - End-to-end suite:
e2e/runs 162 hand-written rules on an Android emulator with Shizuku as root, as shell and without Shizuku, through a hook that exists only in debug builds (seee2e/README.md). - New in 0.4: manual rules (a button, a Quick Settings tile, a widget), "Pause automations", sunrise and sunset, the screen, unlock, battery, headphones and music triggers, reading aloud, brightness and HTTP requests.
- Degradation: deny permissions and turn off Shizuku, and check that the app honestly says what it cannot do instead of failing silently.
- Budget (generative rules): set a low limit and verify the audited suppression.
Which logs to look at
- In-app "Log" tab: every fire, suppression (cooldown/duplicate/budget), error and lifecycle event, with an expandable per-action detail. It is the source of truth: if a rule did not fire, the reason code is there.
- "System" screen: transport/provider status, Shizuku, permissions, battery exemption — useful to attach to a report.
- For crashes:
adb logcatfiltered ondev.argus.
How to report
Open an Issue with: what you asked in chat (exact text), the rule as shown on approval, what you expected, what happened, the relevant lines from the in-app Log, Android version and OEM, Shizuku status. The in-app log contains no message texts and no personal data: it is safe to paste.
Active development. Phases completed and verified on a real device: P0 (engine core + Android glue), P1 (notifications/WhatsApp generative replies), P2 (background triggers: SMS/OTP, calls, connectivity/power/BT, geofence), P3 (parametric state readers, sensor triggers, base tier without Shizuku, multi-provider with per-provider budgets and usage tracking), and P4 variables, deterministic structured flow, resolved model capture and nested delivery. Since v0.2.0 the app is fully bilingual (English/Italian, follows the system language).
Short roadmap:
- Next — fixes and small features from user feedback; no large phase is planned after the 0.7.0 redesign.
Disclaimer: this is a personal project, developed and tested mainly on a single device (OnePlus 15, Android 16) and on an Android 16 emulator. Expect rough edges on other OEMs — that is exactly the feedback we are looking for. No warranty: rules execute real actions on your phone, read what you approve.
Argus is released under the GNU General Public License v3.0 (GPL-3.0). You are free to use, study, modify and redistribute it; derivative works must remain open under the same license.