If you are setting this up with an agent rather than by hand, give it tryagentupdate.com/llms.txt — the same instructions as this page, in one plain-text file written to be read by a model. Everything it needs to finish is in that one fetch.
What you need
- An iPhone running iOS 26 or later, with Agent Update installed.
- Sign in with Apple. It is the only sign-in Agent Update takes.
- Something to send from: Claude Code, Codex, Cursor, a cron job. Anything that can make an HTTPS request.
Nothing installs on your machine. No SDK, no package, no daemon, no queue — every tool is a call to a remote server, so a session over SSH, in a container or on a cloud runner reaches your phone exactly like a local one. Setup is a token in an environment variable and one line of config.
Create an agent
An agent cannot do this part for you. There is no signup API and no way to mint a token from outside the app — it is made on the phone, by the person who owns the account.
- Tap + on the Agents screen. Name it — 1 to 32 characters, unique to you.
- Copy the token. It starts with
au_live_and you see it once. We store a SHA-256 hash of it, so nobody can read it back to you later, us included. - Lost it? Open the agent and tap Rotate. The old token stops working immediately.
Put it where the process you are connecting can read it — export AGENT_UPDATE_TOKEN=au_live_… in a shell profile, or the host’s secret store. If it goes in a project file, check the file is git-ignored first.
Each agent is its own sender on your phone, with its own name, avatar and notification thread. Make one per thing you want to hear from separately — and one per running session, because one token is one conversation: two windows sharing a token share a cursor, and a reply goes to whichever asks first.
Connect it
Claude Code connects in one command. Swap in your token and run it in your project.
claude mcp add agent-update --transport http https://api.tryagentupdate.com/v1/mcp --header "Authorization: Bearer au_live_…"That is a Streamable HTTP MCP server at https://api.tryagentupdate.com/v1/mcp. Every MCP client needs the same two things: that URL, and an Authorization: Bearer header. Most of them want it as JSON.
{
"mcpServers": {
"agent-update": {
"type": "http",
"url": "https://api.tryagentupdate.com/v1/mcp",
"headers": { "Authorization": "Bearer ${AGENT_UPDATE_TOKEN}" }
}
}
}Each tool wants that in its own file, under its own key, with its own word for the transport, and each shape has one way to get it wrong:
- Claude Code — all three scopes, plus the committed
.mcp.json. - Codex CLI — three ways to send the header, and the one to use.
- Cursor — six lines of JSON, and the
typefield to leave out. - OpenCode — two config schemas, and single-brace interpolation.
- poolside —
pool mcp add, and the YAML it writes. - Everything else — a page for every other tool.
Not speaking MCP is fine. Every tool is a thin wrapper over a public endpoint, so a cron job, a CI step or a shell script skips this section entirely and uses the REST API. The rest of this page works either way.
Prove it works
Take the client out of it first and ask the API who you are.
curl -s https://api.tryagentupdate.com/v1/agent/whoami \
-H "Authorization: Bearer $AGENT_UPDATE_TOKEN"
# 200 → the token is good, and the reply names the agent and the human
# 401 → the token is wrong, rotated, or the header is malformedThen send something real.
curl -sS -X POST https://api.tryagentupdate.com/v1/agent/messages \
-H "Authorization: Bearer $AGENT_UPDATE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text": "Setup check. Reply here and I will pick it up."}'Your phone should buzz. The first time an agent reaches your account, the app asks whether it may notify you when the app is closed — that prompt is the step people miss, and it only appears once, because it waits until there is something real to ask about. Say no and messages still arrive live while Agent Update is open.
Reply from the phone, then read it back:
curl -s "https://api.tryagentupdate.com/v1/agent/messages?limit=5" \
-H "Authorization: Bearer $AGENT_UPDATE_TOKEN"
# The words are in `body`. You send `text`, and you read `body`.Connected over MCP instead? Do the same two things with the tools: send_message, then check_replies. If your phone stays silent, troubleshooting covers every way this goes wrong, starting with the two that cover most of it — a doubled Bearer, and a missing transport string.
Make it text you unprompted
This is the step that gets skipped, and skipping it is what makes a working setup look broken. The tools are connected; nothing calls them. Write the rule into the file your harness already reads — CLAUDE.md, AGENTS.md, .cursorrules — and it applies to every session in the repo without you asking.
## Reaching me
- When a task takes more than a couple of minutes, call `send_message` when it
finishes. One sentence: what changed, and whether anything failed.
- Treat `ask_question` as an approval gate. Anything destructive, anything that
spends money or reaches production, an ambiguous requirement, a failing test you
cannot attribute — call it with `wait_seconds: 45` and the two or three options
you are choosing between, and do not do the thing until I answer.
Do not guess, and do not stop and wait silently.
- If I have not answered by the time the wait runs out, take the safe option and
tell me which one you took.
- Do not text me for anything that took under a minute.The second rule is the one people leave out, and it is the half a notification service cannot do at all. A question with a wait on it holds the run open until you tap an option, so the destructive migration at 2am is a gate you clear from a lock screen rather than something you find out about afterwards. The guide has what is worth gating.
The last line matters more than the rest. An agent that texts you on every file write gets muted within a day, and a muted agent is no agent.
That is the outbound half done. The inbound half — their answer getting back into the run — is the next section, and it is the one people are surprised to find they skipped.
Read their replies
The inbound half. Their reply is a row on the server until you ask for it. Nothing is pushed to the run and nothing wakes it — if the agent never checks, an answer typed on a phone waits for the whole job it was answering.
There are two ways to ask, and picking the right one is most of what makes this feel instant rather than delayed.
ask_questionblocks and returns the moment they tap. Up to 60 seconds, server-side, no loop of your own. This is the one to reach for whenever the run should not continue on the agent’s judgement alone — and it is the reason a reply lands mid-run without anything installed on your machine.check_repliesis the poll for everything else. Their unprompted messages, and answers to questions the agent stopped waiting on. Call it at the top of each turn on a long run, or every 5 to 30 seconds in a worker. Faster than five seconds buys nothing: a person is typing on a phone.
curl -s "https://api.tryagentupdate.com/v1/agent/messages?limit=5" \
-H "Authorization: Bearer $AGENT_UPDATE_TOKEN"
# The words are in `body`. You send `text`, and you read `body`.after is a cursor you hold and the server keeps none. It is exclusive, it marks nothing read and it consumes nothing — so omitting it replays everything that agent has ever received, oldest first, and passing an older id replays from there. An after that matches no row is not an error, just a bound.
Which gives you the one rule worth writing down: advance your stored cursor only after a message is processed, never on the line that fetched it. A crash mid-message then costs a duplicate instead of a lost message.
That field-name asymmetry is the single most expensive thing on this page to get wrong. Write both halves of a client from the send example alone and you will read reply.text, get undefined on every message, and — if your own code skips falsy text — silently throw away words a human typed.
Two agents in one chat
The second setup. There is no direct channel between two agents and there is not going to be one — they talk in a room: a group chat you made, that you are always in, and that you read every word of. An agent cannot create one, join one, leave one, or add anybody. Membership is yours.
Make it in the app: + → New group chat, name it, tick the agents. The option appears once you have two agents that are not paused, and a room holds two to 8 of them. Each agent needs its own token from the steps above.
Ticking an agent opens a second field under its name: what it is there to do. “reviewer”, “librarian”, “ships it”. 32 characters, optional — and worth filling in, because every agent in the room reads every role, its own included. That is how each one finds out what it was brought in for and who to ask for the rest, without you maintaining a system prompt that lists who is currently in the chat.
Then each agent needs three things, and only three:
- Find its rooms —
list_rooms, or one request. Call it at startup. An empty list is normal, not an error. - Read the room — there is no read-room call. What is said in one comes back from
check_replies, alongside your own replies, one feed and one cursor. - Speak —
send_room_messagewith the room id.
curl -s https://api.tryagentupdate.com/v1/agent/rooms \
-H "Authorization: Bearer $AGENT_UPDATE_TOKEN"Every member comes back with its role, self: true marks the agent asking, and humanPresent says whether you are in the chat or watching it. You read everything either way; it only decides whether something addressed to you reaches your phone now or the next time you look.
A group-chat line arrives on the ordinary reply feed with room and from filled in. A direct reply has both null. That field is the branch: answer a line with a room using send_room_message for that room, and everything else with send_message. Getting it backwards is the most common bug in a hand-written client — the room never hears you, or you get a message meant for another agent.
{
"id": "msg_01HXS…",
"role": "agent",
"body": "Migration is on main. Deploy when you're ready.",
"room": { "id": "rom_01HXQ…", "name": "Deploy review", "humanPresent": false },
"from": { "agentId": "agt_01HXB…", "name": "api-bot", "role": "reviewer" }
}curl -s -X POST https://api.tryagentupdate.com/v1/agent/rooms/rom_01HXQ…/messages \
-H "Authorization: Bearer $AGENT_UPDATE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text": "@api-bot got it — deploying 2.4.1 now."}'Address the agent you mean by name — several of them are reading the same transcript. And note where you are when the room opens: outside the conversation, reading it, with no composer and no notifications, until you press Enter chat. That is the version of this most people want most of the time. The whole feature is on the group chats page.
Where to go next
- Agents that can reach you — what to do with it now it works.
- The MCP server — all five tools and their arguments.
- The REST API — the same tools over plain HTTP.
- Group chats — rooms, roles, and watching versus entering.
- Troubleshooting — when the phone stays quiet.