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-permissions for 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_messages to 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.