Not receiving texts?

All field notes
AgentsiMessageInfrastructure

Building an AI Agent That Lives in iMessage

How Caddy turns an ordinary text thread into a durable agent loop with tools, approvals, and reliable delivery.

By Chris CataneseGitHubLinkedIn

Most AI products begin with a blank text box. Caddy begins with a thread that is already part of your day.

That changes the engineering problem. An assistant in iMessage has to feel immediate like a chat, stay dependable like an integration, and remain careful when a request can change something in another system. We built Caddy around those constraints rather than treating Messages as a thin skin over a chatbot.

One conversation, two transports

Caddy's user-facing channel is iMessage, delivered through interchangeable providers behind one transport interface. Inbound webhooks normalize provider payloads before the agent sees them. Outbound replies return through the active provider for that person.

interface IMessageTransport {
  sendText(input: { handle: string; text: string }): Promise<void>;
}

The important property is not the interface's size. It is the boundary it creates. Conversation orchestration does not need vendor-specific branching, and changing a person's provider does not create a second conversation model.

A turn is more than a model call

When a text arrives, Caddy resolves the sender, loads the conversation context, and starts an agent turn. The agent can answer directly or use connected tools for Gmail, Calendar, Slack, Linear, and other systems.

Those tools share a provider abstraction too:

const provider = providerRegistry.getProvider(integration);
const result = await provider.executeTool(toolName, arguments_, context);

Keeping dispatch separate from orchestration gives the agent one stable tool contract while integrations evolve independently. It also gives us a single place to apply authorization, logging, and failure handling.

Approval is part of the loop

Useful assistants take action, but the safest default is not silent autonomy. Caddy can pause on a consequential tool call, explain the proposed action in the same thread, and continue after the person approves.

That means approval cannot be modeled as a modal attached to a browser session. It is durable conversation state. The user can put their phone away, reply later, and still continue the original turn with the right context.

The channel is not just where Caddy reports results. It is where control stays with the person.

Reliability has to be invisible

Texting sets a high bar for delivery. People do not care which webhook retried, which worker resumed, or which provider carried a reply. They care that one message produces one coherent response.

We use durable conversation records, idempotent boundaries, structured logs, and provider-aware delivery to keep those implementation details out of the thread. The architecture is deliberately layered:

  1. Resolve the sender and active iMessage provider.
  2. Build one provider-neutral conversation turn.
  3. Dispatch tools through stable integration contracts.
  4. Pause and resume safely when approval is required.
  5. Deliver and persist the final response once.

Caddy, your AI assistant in iMessage

What we are building toward

The best agent should require less ceremony over time. It should understand what matters, text first when useful, and make the next action easy to approve or redirect. iMessage gives us a familiar surface for that relationship; the systems behind it make the relationship dependable.

We will use this developer blog to share more of those systems as they evolve. If you are interested in the product side, explore what Caddy can help with. If you are interested in building careful, everyday agents with us, see our open roles.

Building an AI Agent That Lives in iMessage | Caddy Developer Blog