iAgent: Claude Code via iMessage
an AI agent I control from my phone.
I covered workspaces for isolation and tmux for orchestration — persistent sessions, the root agent monitoring everything. iAgent takes that further: instead of SSH’ing in to check on agents, I just text them.
Why not just use Termius? I have it — full terminal on my phone, I can see everything. But iMessage is better UX for this. It’s the difference between “open Termius and drive the work” vs “send a text to get a useful task done.”
the setup #
Claude Code on my laptop. iMessage as the interface. tmux for persistence. SSH as the fallback when I need direct control.
graph LR
Phone[iPhone] -->|iMessage| iMessage[iMessage]
iMessage -->|plugin| CC[Claude Code<br/>iMessage plugin]
CC --> tmux[tmux session<br/>iAgent]
tmux --> Laptop[dkdc<br/>MacBook]
Phone -.->|SSH over Tailscale<br/>fallback| Laptop
- iMessage plugin — Claude Code reads and sends texts. I message my laptop, the agent responds.
- Tailscale — mesh VPN. Phone and laptop always on the same network, no port forwarding needed.
- tmux — sessions persist when I disconnect. Agent keeps running.
- Claude Code — the agent.
--dangerously-skip-permissionsfor autonomous operation. - Termius — SSH from iPhone for when I need direct terminal access. The fallback path.
the script #
The tmux post covered ai and the aliases-to-scripts progression. iAgent just adds one more script — iai:
# iai — start the iMessage agent #!/usr/bin/env bash cd "$CODE" exec ai --channels plugin:imessage@claude-plugins-official \ -- '/imessage' "$@"
This loads up an /imessage skill with context for the iAgent to operate effectively.
tmux new-session -d -s iAgent "iai"
One tmux session, one script, always running.
the control loop #
I text the agent. It works. It texts back.
me: fix the FOUC issue on zorto.dev
agent: Found the root cause — page renders before CSS loads. Fix in PR #87. All theme CSS compiled into one file, switching is instant. Auto-merge queued.
me: ship it
agent: v0.20.2 released. crates.io ✅ PyPI ✅ GitHub ✅
I can be walking my dogs. The agent is compiling Rust, running tests, pushing PRs on my laptop at home.
workspace agents #
For bigger tasks, the iMessage agent spawns workspace agents — isolated repo clones with focused task files:
graph TD
iAgent[iAgent<br/>root] -->|create-workspace| WS1[ws-zortowebapp]
iAgent -->|create-workspace| WS2[ws-dbwebui]
iAgent -->|tmux capture-pane| Monitor[monitors progress]
WS1 -->|PR| PR1[zorto #81]
WS2 -->|PR| PR2[db #20]
iAgent -->|gh pr merge| PR1
iAgent -->|gh pr merge| PR2
create-workspace --task tasks/zorto-webapp.md \ --repo dkdc-io/zorto "zortowebapp"
Same pattern from the tmux post — except now the iMessage agent is the root. It creates workspaces, monitors them, and merges PRs, all while I’m texting it from my phone.
a day’s output #
April 5, 2026. Mostly from my phone:
- 28 PRs merged across 2 repos
- 9 releases
- 3 adversarial code reviews
- 2 web UIs built from scratch
- security hardening (SQL injection, XSS fixes)
- blog post published
- Netlify build failure diagnosed and fixed
Most of that happened while I was out with my dogs.
rough edges #
The iMessage plugin is new and it shows:
- Message echoes. Sometimes my messages echo back as if the agent sent them. The agent has to learn to ignore these.
- Truncation. Long messages get cut off in the channel delivery. The agent has to call
chat_messagesto read the full thread before responding — every time. - Context engineering. The agent needs a skill file (
.claude/skills/imessage/SKILL.md) with conventions, key paths, common commands. Without it, it wastes time on basic discovery every session.
These are solvable. The plugin is a week old. The pattern works even with the rough edges.
limits #
- Product decisions. It asks. “Subprocess or PyO3?” is my call.
- Taste. It ships fast but sometimes sloppy. A FOUC fix took 3 tries. The convention “one correct fix > three iterative fixes” came from that.
- Self-direction without structure. Without task files, journals, and AGENTS.md, it drifts. The context engineering is the hard part.
the kill switch #
When you give an agent autonomous access to your laptop, you need a way to stop everything. I mentioned in the tmux post that one thing stays as a shell alias on purpose:
# .bash_aliases — not on $PATH, intentionally function nuke() { tmux kill-server 2>/dev/null if [[ "$OSTYPE" == "darwin"* ]]; then pmset displaysleepnow else loginctl lock-session fi }
Kill every tmux session, lock the screen. I SSH in from my phone, type nuke, walk away. It’s an alias because agents shouldn’t be able to call it — it’s not on $PATH, so the agent doesn’t even know it exists.
the stack #
graph BT
Context[AGENTS.md + journal + tasks] --> Agent[Claude Code + plugins]
Agent --> Session[tmux persistence]
Session --> VPN[Tailscale mesh]
VPN --> Phone[Termius + iMessage]
Cost: Claude subscription + Termius. Everything else is free.