---
title: "Automate repetitive iPhone tasks with an agent | Mobster"
description: "Build iPhone workflows with triggers, branches and approvals. Run them from an AI agent over MCP, on a schedule or by webhook. Reuse learned steps on repeats."
canonical: "https://mobster.dev/blog/automate-iphone-workflows-with-an-agent"
last-updated: "2026-11-14"
---

Build iPhone workflows with triggers, branches and approvals. Run them from an AI agent over MCP, on a schedule or by webhook. Reuse learned steps on repeats.

# Automate repetitive iPhone tasks with an agent

2 November 2026

Save a task as a workflow to run it on a schedule, from a script, from Zapier or from your coding agent. Mobster saves it as a graph: triggers, steps on your iPhone, branches, loops and a stop where it asks you. Your AI agent runs it with one MCP call, and after the first good run it can reuse the steps it learned. Here is what the graph holds and how a run goes, down to the JSON. For one-off tasks on your own phone, [Automate your own iPhone from a Mac](https://mobster.dev/blog/automate-iphone-from-mac) comes first; this post starts where a task becomes a workflow.

## One workflow, as the file saves it

Every workflow in Mobster for Mac is a graph of nodes joined by edges. This one reads the newest Instagram DM from a person and notifies you when the returned answer contains a question mark:

```
{  "name": "Answer DMs",  "graph": {    "version": 2,    "inputs": [{"name": "who", "type": "string", "default": "Sam"}],    "nodes": [      {"id": "start", "type": "trigger.manual"},      {"id": "read", "type": "phone.task",       "config": {"text": "Open Instagram and read the newest DM from {who}"}},      {"id": "check", "type": "branch",       "config": {"cases": [{"id": "asked", "label": "A question", "when": "contains($input.answer, '?')"}]}},      {"id": "tell", "type": "send", "config": {"to": "notification"}}    ],    "edges": [      {"id": "e1", "source": "start", "sourcePort": "out", "target": "read", "targetPort": "in"},      {"id": "e2", "source": "read", "sourcePort": "out", "target": "check", "targetPort": "in"},      {"id": "e3", "source": "check", "sourcePort": "asked", "target": "tell", "targetPort": "in"}    ],    "limits": {"maxSpendUsd": 2, "maxMinutes": 60}  }}
```

(Node positions on the canvas are left out.) `{who}` is a blank: a run asks for it, and a schedule uses its default, `Sam`. `{who: Sam}` written in a step’s words makes the same blank. A workflow has at most 12.

The node types fall into four groups, and `GET /api/workflows/node-types` lists every field and port.

| Group | Types |
|---|---|
| Triggers | `trigger.manual`, `trigger.schedule`, `trigger.webhook`, `trigger.notification`, `trigger.shortcut`, `trigger.app` |
| On the phone | `phone.task` (your words), `phone.taught`, `phone.check` (looks only), `app.read` (an app pack’s rows, by code) |
| Off the phone | `connector.call`, `send`, `brief`, `transform`, `ai.ask`, `output` |
| Flow | `branch`, `loop`, `merge`, `wait`, `approval`, `workflow.call` |

## Validate a workflow before saving

A graph must pass validation before Mobster saves it. `POST /api/workflows/validate` runs the same check every save runs, and answers `400 invalid_graph` with each problem and the node it is about. These are the answers the real validator gave on 17 October 2026 for the graph above and two broken copies of it:

```
answer_dms: saved, 4 nodes, 3 edgeswith a cycle: 400 invalid_graph    {"node": "read", "message": "These steps wait for each other in a circle: “Phone task”, “Branch”, “Send”"}with an unwired node: 400 invalid_graph    {"node": "lonely", "message": "“Wait” never runs: no trigger leads to it"}
```

It also refuses a port a type doesn’t have, an edge whose value the next port can’t take, an expression that can only fail, and anything past 100 nodes, 300 edges, loops 2 deep or 200 rounds. An expression such as `{= count($node.read.rows) }` runs no code: it is JSON values, operators and a fixed list of functions, and comparing text with a number is an error, so a branch never guesses.

## How a graph runs

- **Order.** A node runs when everything wired into it has finished. A node on a path that wasn’t taken is skipped. Nodes that are ready at the same moment start in a fixed order.
- **Phones.** Steps on different phones run side by side, at most 8 at once. Steps on the same phone run one at a time.
- **Each phone step is an ordinary task.** Its approvals, your phone’s rules and the per-task and monthly limits all apply.
- **Spending.** Each phone step reserves its share of what is left of the workflow’s limit before it starts, so steps running side by side can’t spend past it together.
- **What a step read from a screen** reaches a model only as its input, marked as data. Saving refuses anything that would make screen text a step’s instructions: “Screen text can’t become a step’s instructions.”

You can see an estimated cost before a run. `GET /api/workflows/{id}/estimate` answers with a range per node and says where each number came from: the node’s last 5 runs (`history`), an estimate for a step that hasn’t run, `replay` for a step that costs nothing, or `none` for a node that calls no model.

```
{"estimate": {"nodes": {   "read": {"lowUsd": 0.03, "highUsd": 0.05, "modelCalls": 2, "basis": "history", "runs": 5, "decides": "model"},   "each": {"lowUsd": 0.06, "highUsd": 0.24, "modelCalls": null, "basis": "estimate", "rounds": 3}}, "total": {"lowUsd": 0.09, "highUsd": 0.29, "modelCalls": null, "partial": false, "overLimit": false}}}
```

A number Mobster can’t estimate is `null`, never `0`. That is the example from the workflow docs, not a measurement.

## Run it from your agent

The Mobster MCP server has two tools for this. `list_workflows` returns each saved workflow’s name, id, steps, blanks and graph. `run_workflow` takes the workflow’s name or id and a value for each blank:

```
{"workflow": "Answer DMs", "values": {"who": "Sam"}, "wait_seconds": 30}
```

For a graph it follows the run until it ends or `wait_seconds` pass, then answers with the status, the workflow’s result and each step’s state. A workflow you switch on under **Run from other apps** appears as its own tool, `workflow_answer_dms`, with its blanks as typed inputs. It’s off by default so your agent’s tool list stays short. The same workflow is a REST route:

```
curl -X POST http://127.0.0.1:8765/v1/workflows/answer_dms \  -H "Idempotency-Key: dms-2026-10-16" -H "Content-Type: application/json" -d '{"who": "Sam"}'
```

```
{"workflowId": "aaaaaaaaaaaa", "status": "completed", "result": {"sent": 2}, "answer": null, "runId": "000000000001", "batchId": "cccccccccccc", "steps": [{"instance": "read", "type": "app.read", "state": "done"}]}
```

That answer is the one the workflow docs print. From a terminal, `mobster chat --workflow "Answer DMs" --set who=Sam` prints each step’s state as it changes and exits 0 when the run completed, 1 when it didn’t, and 4 when it still runs after `--wait` seconds. `mobster workflow show "Answer DMs"` prints the steps in the order they run (the output below is from the docs):

```
$ mobster workflow show "Answer DMs"“Answer DMs” (aaaaaaaaaaaa): published revision 2, 4 steps  start  Manual (trigger.manual)  read   Read an app (app.read), after start  ok     Ask me first (approval), after read: “Reply to them?”  reply  Phone task (phone.task), after ok (approved): “Reply to {who}”
```

[The MCP server docs](https://docs.mobster.dev/mcp-server) list the rest. `run_workflow` is one of the tools that drives a real iPhone, and it uses only the apps you let agents outside Mobster use.

## Start it without you

A workflow starts from whatever trigger its graph has.

- **A schedule.** `trigger.schedule` takes a cron expression and a time zone. `missed` decides what happens to a slot the Mac slept through: `skip` (the default) skips it, `run_once` runs it once on wake. `overlap` decides what happens to a slot that comes round while the last run still goes: `skip`, or `queue_one`, which keeps one waiting.
- **A webhook.** **Make a URL** on the workflow gives another tool a signed address. Zapier, Make, n8n or a script sends JSON, and each field fills the blank of the same name. The signature is `t=<unix seconds>,v1=<HMAC-SHA256 over t, ".", and the body>`:

```
BODY='{"person": "Sam", "minutes": "15"}'T=$(date +%s)SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')curl -s "$URL" -H 'Content-Type: application/json' -H "Mobster-Signature: t=$T,v1=$SIG" -d "$BODY"
```

```
{"ok": true, "message": "Started Running late.", "filled": ["person", "minutes"], "ignored": []}
```

A call with a `t` more than 5 minutes off your Mac’s clock is `401 stale`, a repeated call is `409 replayed`, and more than 10 calls a minute to one URL is `429`. [Webhooks](https://docs.mobster.dev/webhooks) lists every answer.

- **A notification.** `trigger.notification` starts the workflow when your phone gets a notification that matches an app and some words. It starts at most 6 times an hour, and one that arrives mid-run waits behind it.
- **A new row.** `trigger.app` reads an app with its pack every 5 to 1,440 minutes, by code, and starts one run per row it hasn’t seen, oldest first. Its first read only remembers what is there.
- **A Shortcut.** `POST /api/workflows/{id}/run` with `"trigger": "shortcut"` starts it from Siri, Shortcuts, Raycast or Alfred.

Whatever comes in through a trigger is data. A webhook’s `body` is masked and cut to 16 KB, and a later step reads it as `{= $node.hook.body.order }`, never as an instruction.

## Approvals and failures

Every phone step is a task. By default, Mobster asks before a send, post, delete or purchase, including in repeated and scheduled steps. An `approval` node adds a stop of your own. Its message shows values from earlier steps, it appears as a Mac notification, and it can show the last screen a step kept. **Approve** takes the `approved` path, and you can edit the value first. **Decline** takes `declined`. No answer within `timeoutMinutes` (24 hours by default) counts as a decline.

An agent can’t answer it. A call over MCP, Mobster Remote or a webhook gets `403 app_only`. A scheduled step that needs approval waits for you, and if nobody answers within 10 minutes, the task ends without that step.

Failures are explicit too:

- Without an error edge, everything that depends on the failed step stops, and paths that don’t depend on it carry on.
- With one, the failure is caught and the error path runs with `{node, reason, status, runId}`.
- A step that may have sent, posted or bought is never retried. `retry` applies only to steps that look, read or wait.
- A step whose outcome Mobster can’t know is `unknown`. It is never retried, and what depends on it stops.
- A step is written to the run’s log before it starts. If Mobster quits mid-run, the run ends `interrupted` at the next launch and is never run again.

The same rules come from the open-source engine: the journal and the duplicate guard are MIT-licensed code in [the Mobster engine](https://github.com/RadishSoftware/mobster) (`mobile_agent/journal.py` and `mobile_agent/effect_ledger.py`). The workflow graph and its canvas are part of Mobster for Mac, not that repository.

## Repeat the steps it learned

After a workflow’s first good run, Mobster keeps the steps it took. A phone step has a `mode`:

- `auto` (the default) reuses its learned steps when the screen fits, and the model takes over from the step where it doesn’t.
- `replay` runs only the learned steps, and stops with “set to replay only” if one is missing or its screen differs.
- `agent` uses the model every time.

Typed text comes from the workflow’s own words, so `Text {name}: {message}` types the message you fill in. Text the model wrote, such as a caption, is written by the model on every run. A replay says done only on evidence: the screen shows what the learned run quoted at the end, or it ends on the learned run’s last screen with the words it typed on it. Otherwise the run ends with **Check the result**.

Replay does not hand an action with an unknown outcome to the model for another attempt. How a replay matches a screen and heals a changed step is in [Replay and heals](https://docs.mobster.dev/testing).

Measured on an iPhone 15 Pro, iOS 26.0.1, where every run passed and every approval was asked as before:

| Task, repeated with the same words | No learned steps | Learned steps | Model calls per run |
|---|---|---|---|
| Open Settings, General, About | 7.87 s | 3.91 s | 3 to 0.25 |
| Open the Stopwatch tab in Clock | 4.37 s | 1.10 s | 2.5 to 0.25 |

In these runs, the first repeat made one model call to check the screen. Later repeats made none. These measurements cover the two short tasks above.

## Build it from words

`POST /api/workflows/draft` with `{"words": "Every morning, read my Instagram DMs and thank everyone who wrote"}` returns a draft graph and saves nothing. With an Anthropic key, Claude Haiku 5.5 plans it from the node types, so an app read, a loop and an approval arrive as steps and not as prose. Mobster builds the graph by code and checks it as a save would. If the check finds problems it gives the drafter the list, at most twice. Without a key, a rule turns a leading or trailing phrase such as “every weekday at 9am” into the schedule and each sentence into a step.

Drafts are separate from what runs. A graph you save is a draft. The run that a schedule, a webhook, MCP or Remote starts is the published one, until you publish. Mobster keeps the newest 100 saved versions. A rollback saves an old version as the newest. A test run executes the draft on your phone, approvals included. `pin` freezes a node’s output so you can work on the steps after it, and a re-run from a step reuses every step before it.

## Try it

1. [Download Mobster for Mac](https://mobster.dev/download) and set up your iPhone.
2. Run a task once, and under its answer choose **Save as workflow**. Mobster drafts the name, the steps and the blanks.
3. On the workflow’s page, turn on **Run from other apps**, then connect Claude Code or Codex from Settings, Connectors.
4. Ask your agent to run it. [Mobster in Claude Code](https://mobster.dev/connectors/claude-code) has the setup, and [the workflow builder docs](https://docs.mobster.dev/workflows) describe every node.

Workflows run while your Mac is awake and your iPhone is plugged in and unlocked. Mobster never unlocks the phone for you.

## Questions

- ### Can an AI agent run a workflow on my iPhone?
  Yes. Your agent calls list_workflows to see your saved workflows and run_workflow with a value for each blank. The run happens in Mobster for Mac on your phone. Approvals for sends, posts, purchases and deletes stay with you in the app, and an agent can't answer them.
- ### Does an iPhone workflow call a model on every run?
  The first repeat makes one model call to check the screen. Later repeats can reuse the learned steps with no model call when each screen still matches. A step whose screen changed goes back to the model, and a replay never takes over after a send, post or delete whose outcome is unknown.
- ### Can a workflow start by itself?
  Yes. A workflow can start on a schedule, from a signed webhook call, from a Shortcut, when a notification arrives on your phone, or when a new row shows up in an app. It runs while your Mac is awake and your iPhone is plugged in and unlocked, and a slot the Mac missed is skipped unless you set it to run once.
- ### What stops a workflow from sending something twice?
  A step is written to the run's log before it starts. If Mobster quits mid-run, the run ends as interrupted and is never run again, and a step whose outcome is unknown is never retried.

## Sources

- [Mobster docs: build a workflow on its canvas](https://docs.mobster.dev/workflows)
- [Mobster docs: workflows, blanks, schedules and learned steps](https://docs.mobster.dev/schedules)
- [Mobster docs: webhooks and starting a workflow from another tool](https://docs.mobster.dev/webhooks)
- [Mobster docs: MCP server (list_workflows and run_workflow)](https://docs.mobster.dev/mcp-server)
- [Mobster docs: approvals and safety](https://docs.mobster.dev/safety)
- [Mobster on GitHub: the engine under every phone step (MIT)](https://github.com/RadishSoftware/mobster)

## Try it on your own iPhone

Download Mobster for Mac

## More from the blog

### [Automate iPhone tasks from your Mac, with approvals](https://mobster.dev/blog/automate-iphone-from-mac)

7 Oct 2026

### [Give Claude Code a real iPhone](https://mobster.dev/blog/claude-code-real-iphone)

9 Oct 2026

### [Can ChatGPT control my iPhone?](https://mobster.dev/blog/can-chatgpt-control-my-iphone)

13 Nov 2026
