# Calling the agent

Canonical: https://suzo.app/docs/calling-the-agent

Agent-initiated calling uses MCP. To start a call from the iPhone while an agent is idle, Suzo needs a receiver that can resume that same agent session. This is optional and harness dependent.

## Wake, then join

The phone first reserves an outgoing call. Suzo sends an authenticated webhook to a receiver bound to the original session. That session then uses MCP `call_user` with the **same request UUID** to join the existing call, without a second ring. A receiver’s queued acknowledgment does not mean the call is connected.

<figure class="docs-wake-flow" role="img" aria-label="Wake path: iPhone reserves a call, Suzo sends an authenticated webhook, the receiver queues and resumes the bound agent session. Join path: that same session calls call_user through MCP with the same request UUID; Suzo joins the reserved iPhone call without a second ring.">
<div class="docs-flow-lane"><strong>1. Wake the session</strong><span>Authenticated receiver webhook</span></div>
<div class="docs-flow docs-flow-wake docs-flow-four">
<div class="docs-flow-node"><strong>iPhone</strong><span>Reserve call</span></div>
<div class="docs-flow-edge"><b aria-hidden="true">→</b></div>
<div class="docs-flow-node docs-flow-node-suzo"><strong>Suzo service</strong><span>Wake webhook</span></div>
<div class="docs-flow-edge"><b aria-hidden="true">→</b></div>
<div class="docs-flow-node"><strong>Receiver</strong><span>Validate · queue</span></div>
<div class="docs-flow-edge"><b aria-hidden="true">→</b></div>
<div class="docs-flow-node"><strong>Original session</strong><span>Same tools · permissions</span></div>
</div>
<div class="docs-flow-lane"><strong>2. Join the call</strong><span>MCP · same request UUID</span></div>
<div class="docs-flow docs-flow-wake">
<div class="docs-flow-node"><strong>Original session</strong><code>call_user</code></div>
<div class="docs-flow-edge"><b aria-hidden="true">→</b></div>
<div class="docs-flow-node docs-flow-node-suzo"><strong>Suzo service</strong><span>Match request UUID</span></div>
<div class="docs-flow-edge"><b aria-hidden="true">→</b></div>
<div class="docs-flow-node"><strong>iPhone</strong><span>Join reserved call</span></div>
</div>
</figure>

## Bind a supported receiver

Use documented extension points or a thin protocol adapter. Bind one Suzo agent ID to one explicit session, profile, or workspace. Preserve the runtime’s permissions and approvals.

The receiver needs a separate high-entropy bearer secret of at least 32 random bytes. Its endpoint is reached by the **Suzo server**, not the phone. Use HTTPS, or HTTP loopback when both services share a host. Private network endpoints work only if the server can reach them.

Do not expose an unauthenticated runtime control API. Never let a webhook payload choose an arbitrary shell command or session.

## Implement protocol version 3

Suzo POSTs a versioned envelope with bearer authentication:

```json
{
  "version": 3,
  "type": "suzo.hook.check",
  "request_id": "<request-UUID>",
  "agent_id": "<bound-Suzo-agent-UUID>",
  "requested_at": "<ISO-8601-timestamp>"
}
```

Validate the secret, version, type, UUIDs, and bound agent. Return HTTP 200 or 202 with the exact request UUID:

```json
{"version":3,"accepted":true,"request_id":"<same-request-UUID>"}
```

A `suzo.hook.check` probe must not wake a session, call the phone, start an LLM turn, or send a message. A generic HTTP 200 is not sufficient.

For `suzo.call.request`, durably queue once per request ID, acknowledge promptly, then wake the fixed session. Deduplicate across restarts and serialize work while it is busy. The resumed agent calls `call_user` with the exact received request ID and runs the normal [call loop](https://suzo.app/docs/call-loop).

Accepted means queued, not connected. Verify runtime admission and report wake failures through the request-status endpoints in the [complete receiver contract](https://suzo.app/docs/receiver-reference).

## Register and check

Read `GET /v1/agent/calling` first and preserve a working receiver. Use the agent credential to register:

```http
PUT /v1/agent/calling
Authorization: Bearer <scoped-agent-token>
Content-Type: application/json

{
  "url":"https://<your-runtime>/suzo/call-request",
  "token":"<separate-receiver-secret>",
  "protocolVersion":3
}
```

Then POST `/v1/agent/calling/check`. Status readback is redacted; the secret is never returned. Replacing the URL requires the corresponding secret again. Existing version-1 Prism and version-2 Suzu receivers keep their configured wire values and acknowledgments; version 2 continues using `suzu.hook.check` and `suzu.call.request`. Do not silently upgrade a working receiver, re-pair a contact, or replace its credentials. New setup explicitly registers `protocolVersion: 3`.

## Check the session, not just the endpoint

A stock Hermes webhook may create a separate session. A separate Codex App Server process does not automatically control an existing desktop chat. Inspect the installed runtime and confirm the adapter targets the owning session.

If the harness cannot safely wake that session, leave this feature unconfigured. The agent can still initiate a call. Never install a dummy receiver or describe a successful probe as an answered call.
