Workflows: Automate Dev Tasks on Android, No Server Needed
Automate tasks on Android with PocketCode Workflows: 25 triggers, branches and loops, cron with time zones, per-workflow permissions and encrypted secrets. No server, all on-device.
Every developer accumulates the same little chores: run the tests after a push, back up the database when a deploy lands, format on save, tell me when a build breaks. On a laptop you wire those together with CI runners, cron jobs and a couple of shell scripts on some server. On a phone, the usual answer is «you can't». Pocket Code changes that with Workflows: an event-driven automation engine that lets you automate tasks on Android entirely on the device, with no server to rent and nothing to keep alive in the cloud.
A workflow is simple to describe: a trigger (an event inside the IDE, or a schedule) plus a sequence of steps that run over a shared context. That is enough to build real dev workflows — the kind you would normally stand up a CI pipeline for — right next to your editor, your terminal and your database manager.
Four ways to make one
You never have to start from a blank canvas:
| Mode | How you make it | Example |
|---|---|---|
| Template | Tap Activate on one of the 17 built-in templates | "Tests, then deploy if they pass", "Auto-deploy on push to main", "DB backup after deploy" |
| Describe it to the AI | Write a sentence and get an editable scaffold | "when there's a push to main, deploy and notify" becomes a workflow with its failure branch |
| Editor | The "+" button for the full form | Build it from scratch, with expressions and a retry policy |
| Import a file | Open a .pcwf.json someone sent you | It arrives disabled and with a new id, so nothing runs until you have read it |
Activating a template clones it into an editable copy with a fresh id, so tweaking it never touches the original.
Triggers: what wakes a workflow up
Workflows listen to the events flowing through the IDE. The engine subscribes to the app's internal bus and maps each event to a trigger type. There are twenty-five types, and a workflow can carry one main trigger plus as many extra ones as you want:
| Family | Triggers |
|---|---|
| Git | Push, commit |
| Files | Saved, created, deleted, renamed |
| Project and build | Project opened or closed, build finished, run finished, terminal command finished |
| Deploy and data | Deployment completed, deployment failed, database query executed |
| Other modules | API Tester response, design exported, the AI agent touched a file, the AI agent finished a turn |
| From the phone itself | Shortcut, Quick Settings tile, shared text, shared file, app foregrounded |
| Chaining and manual | Error detected (another workflow failed), manual or scheduled |
The filter is typed, and that is a real change from earlier versions: every trigger declares its fields, and you compare a field against a value with operators — equals, contains, starts with, matches, path globs. It is no longer a substring search over the event's text. And because nobody gets a filter right first time, the editor has a capture the next event button: you do the thing, and it shows you the real payload the trigger carries so you can write the filter against something that exists.
Not every type has something emitting it yet. Rather than hide those, the problems panel warns you when a workflow uses a trigger that would never fire today — a trigger that exists and cannot be used is worse if nobody says so.
Scheduling: cron on your phone, no server
This is where «serverless automation» gets concrete. Any workflow can carry one or
more schedules: an interval, or a
five-field cron expression with wildcards *, lists
N,M, ranges N-M and steps */N. As you type, the screen shows you the next
fire time or tells you the expression is wrong.
Three things that schedulers like this usually lack, and that are here:
- A time zone per schedule, with daylight saving resolved. In spring, at the hour that does not exist, it fires on the jump; in autumn, at the hour that happens twice, it fires once. It is tested.
- A policy for what you missed. You choose: skip whatever fell while the device was off, or catch up once when it comes back. Once — not the forty-eight of a long weekend.
- Device conditions. Any network, Wi-Fi only or none; only while charging; not on low battery; only while the device is idle. A condition delays the run, it does not cancel it, and the screen says so in those words.
And one uncomfortable honesty: Android does not run background work more often than every fifteen minutes. An interval below that is raised and the app gives you the exact number. A cron below it is nobody's to raise, so the only true thing to say is that there is no guarantee — and that is what the screen says, instead of a number that would be made up.
Schedules are persisted and queued again when the app starts, so a process death does not quietly empty your automation.
Branches, loops and sub-workflows
The editor is not a list of steps in a row. There are seven kinds of step, six of them control flow, and branches nest inside branches: what you build is a tree.
- If / else — two branches. The condition is a tree, not a single slot:
And,OrandNotnest, so «the tests passed and the branch is main» is one condition, not two workflows. - Parallel — N branches at once, joined one of three ways: when all finish, with the first that finishes, or with the first that finishes well. The last two cancel the losing branches so they stop firing side effects.
- For each — iterates a list with a per-item alias, a safety cap on iterations and an optional number of iterations to run at once.
- Try / on failure — if anything in the first block fails, the second runs with the error message bound to a variable you name.
- Sub-workflow — runs another whole workflow inside this one, with its own inputs. It is what turns a long workflow into reusable pieces.
- Wait — for a time, or until an event happens with the time as the cap. «Wait for the deploy to finish» rather than «wait two minutes and hope».
And without building a control step at all: every step carries an optional condition of its own. If it is false the step is skipped, without wrapping it in a whole if / else.
Fifteen actions, every one wired
There are fifteen action steps, and all fifteen are connected to something that really runs: run a command, an HTTP request, a git operation, a deploy, a database query or backup, read, write or check a file, open one in the editor, analyse or generate with AI, transform a value, set a variable, and notify.
That all fifteen have an implementation is not a presentation detail: a test goes red naming any type that loses one. A type with nothing behind it fails loudly instead of quietly reporting success.
Whether a step is destructive is worked out with its parameters in hand, not
from its type: git status is not git push, and creating a new file is not
overwriting one. Labelling the whole type destructive would mean confirming a
status, and asking for confirmation on everything teaches people to accept
without reading.
Passing data between steps
Steps share a context, and any field can reference another with an expression in double braces:
| Expression | Resolves to |
|---|---|
{{var.name}} | A declared input or a value a step set |
{{step.<id>.status}} | A step's status |
{{step.<id>.output.json.items[0].name}} | A field of the output, navigating inside JSON |
{{step.<id>.error}} | A step's error message |
{{item.<alias>}} | The element of the live iteration |
{{trigger.<field>}} | The typed payload of the event that fired it |
{{secret.NAME}} | A stored secret. Terminal: you cannot navigate inside one |
Plus thirteen chainable filters: lines, first, last, trim, upper,
lower, length, json, default, split, join, date, replace. So the
first line of a command's output is
{{step.sh.output.stdout | lines | first | trim}}.
An expression that does not resolve returns an empty string on purpose, so a reference to a step in a branch that never ran does not fell the whole run. If you want the opposite, there is a per-workflow switch: in strict mode, failing to resolve is an error with a reason, and the reason reaches the run detail.
Reliability: retries, timeouts and what to do on failure
Every step takes a retry policy (with a fixed, linear or exponential wait and a cap on that wait), a timeout, and a policy for what to do if it fails: stop the run or carry on.
Two details that are expensive to discover the hard way:
- Carrying on after a failure does not tint the run. The step stays red with
its error, but the run comes out green — the same distinction
continue-on-errormakes in GitHub Actions. - A timeout is not retried. It comes back marked as timed out and that result is returned as it is.
And a step can be switched off without deleting it, which is what you want while debugging, instead of deleting it and typing it again.
On top of that, rather than running the whole workflow — with its deploys and its notifications — every time you tweak a parameter, you can test a single step with the pinned data as its context. A step that cannot be simulated is not simulated: the button then says «run this step for real» and asks first. A button that says «fine» without having run anything is worse than no button.
Permissions and secrets
A workflow declares what it is allowed to do: go out to the network, write files, run commands, and a list of domains it may reach. The implementations check before they touch anything, and a refusal says which permission it was, what was about to run and where to change it. A sub-workflow inherits the intersection of the permissions: it cannot open what the parent closed.
It is not a sandbox, and the screen says so: anyone who can edit the workflow can
open the belt. It protects against carelessness — a workflow you opened to the AI,
one someone else sent you, or the curl somebody pasted «just to try» and left
there.
Secrets live in an encrypted store, per account. You reference them as
{{secret.NAME}} and the value is never shown again, not even on its own screen.
They are only resolved in a real run; the masking covers every stored value, not
only the ones a step read; and it happens before writing to the database, not
when drawing the screen — masking at paint time would be too late, the value would
already be in the file and in any backup.
Run history you can actually inspect
Every run is written down whole, with each step's result, how long it took, how many attempts it needed and its error if it had one. It survives crashes and the app closing. The Activity tab is the view across every workflow, filtered by status, by date, by workflow and by the text of the error.
Retention is yours to set: 100 runs and 30 days by default, anywhere from 1 to 1,000 runs and 1 to 365 days.
Sharing one
A workflow travels as a .pcwf.json file. What does not travel: the history,
the pinned outputs, the schedules and the on/off switch. What arrives is disabled
and has a new id.
Parameter values marked secret are emptied before the file is written and
listed, so whoever opens it knows what they have to fill in. A {{secret.X}}
reference does travel: it is a name, not the secret, and emptying it would break
the workflow beyond repair.
Describe it in plain words
If you would rather not assemble the steps by hand, write a sentence. The model's answer is repaired where it can be, run through the linter, dry-run, and the result is shown to you before anything is saved. You can also ask it to change one you already have: the answer is merged on top, keeping its id, its switch and its date, and you get the diff.
If you have no model configured there is a local heuristic fallback, which is the only thing that works for someone who has not put in a key yet. The document sent to the model carries no secret parameter values — they are replaced by a marker.
And the other way round: any workflow you tick as «available to AI agent» is exposed as a tool in the chat, and the agent can call it.
Your automation stays on your device
Definitions, history and schedules live in a local database on your phone.
Definitions can be backed up to the cloud for restore, but nothing is ever read
from the cloud to run your automation: the engine runs locally, full stop.
History and schedules do not sync at all: history is device-local, and schedules
are device-specific WorkManager state.
One last note of honesty, in case you are comparing with n8n or Make: branches here are a nested tree, not nodes you wire on a canvas. There is no N-way switch and no way to join a branch to a specific node further down. There is no webhook trigger either, and workflows cannot yet be installed from or published to the Marketplace: today, sharing one means sharing the file.
PocketCode is on its way to Google Play, bringing a complete automation engine — twenty-five triggers, branches, cron on your phone, permissions and secrets — to the same app where you write and ship the code. Join the pre-registration to be among the first to try it on your own device.
Workflows