How do I build an iMessage agent that actually does things?

Short answer. Use Photon's spectrum-ts SDK to receive and send iMessages, and AgentWeb to run the web actions. In the message loop, your model turns a text into a task. Your code calls AgentWeb's /api/execute with a domain, an action and params, and sends the result back into the same conversation. Anything paid or public waits for the user's yes.

Steps

  1. Run npm install spectrum-ts and set SPECTRUM_PROJECT_ID and SPECTRUM_PROJECT_SECRET from your Photon dashboard.
  2. Get an AgentWeb aw_ key and set AGENTWEB_API_KEY.
  3. Write a decide() function with your own model that returns either a reply or an action (domain, action, params). Use AgentWeb's list_actions to give the model the real action names.
  4. Call AgentWeb for actions, and send the result back with space.send.
  5. For paid or public actions, ask first and only run them after a yes.

The code

import { Spectrum } from "spectrum-ts";
import { imessage } from "spectrum-ts/providers/imessage";
import { decide } from "./decide"; // your model: returns { reply } or { domain, action, params, needsYes }

async function agentweb(domain: string, action: string, params: object, key: string) {
  const res = await fetch("https://mcp.agentweb.us/api/execute", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.AGENTWEB_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": key,
    },
    body: JSON.stringify({ domain, action, params }),
  });
  return res.json(); // { ok, result, credits_charged, balance_remaining, support_id }
}

const app = await Spectrum({ providers: [imessage.config()] });

for await (const [space, message] of app.messages) {
  if (message.content.type !== "text") continue;
  const step = await decide(message.content.text);
  if (step.reply) { await space.send(step.reply); continue; }
  if (step.needsYes) { await space.send(step.confirmText); continue; } // run it on the next "yes"
  const out = await agentweb(step.domain, step.action, step.params, step.idempotencyKey);
  await space.send(out.ok ? step.summarize(out.result) : "That didn't go through, and you weren't charged.");
}

The decide() module and its confirmation state are yours to write. They're specific to your model and your product.

Frequently asked questions

How do users connect their accounts?

Your agent calls agentweb_start_connection and texts the user the link. They sign in once, and your agent never sees the password.

Does this work for WhatsApp too?

Yes. Photon's spectrum-ts also has a WhatsApp Business provider, and the AgentWeb side stays the same.

Sources (accessed Oct 20, 2026): https://photon.codes/docs/spectrum-ts/getting-started , https://www.agentweb.us/install.md

More: Sites AgentWeb can act on · All guides · Glossary · Install AgentWeb