---
title: "PatterTool — phone as a tool"
description: "Wrap a Patter instance so external agents (LangChain, OpenAI Assistants, Anthropic Claude, Hermes, MCP) can place real phone calls as a single tool invocation."
icon: "phone-arrow-up-right"
---

# PatterTool

`PatterTool` exposes a live `Patter` instance as a single **`make_phone_call`** tool that any text-based agent framework can register and dispatch. The agent picks up the phone, Patter runs a short conversation against the caller, and the tool returns a JSON envelope with the call ID, status, transcript, cost, and metrics.

This complements the **bring-your-own-agent** pattern (`serve(on_message=...)`) with the **inverse pattern** — phone-as-tool — exposing any Patter-built voice agent as an MCP-compatible tool that other agent frameworks can call as if it were a local function.

| Pattern | Where the brain lives | Where Patter sits | Use this when… |
|---|---|---|---|
| **A — Bring-your-own agent** (HTTP/WS endpoint) | Customer's existing service | Patter is STT + LLM proxy + TTS | Customer already runs a voice agent and wants to swap the voice transport but keep their conversation logic. Use `serve(on_message='https://...')`. |
| **B — Phone as a tool** (this page) | Customer's text-based agent | Patter is a tool the agent calls | Customer's agent is text-driven but needs to make phone calls during a conversation. |

## Quickstart

```python
import asyncio
from getpatter import Patter, Twilio, DeepgramSTT, AnthropicLLM, ElevenLabsTTS, PatterTool

phone = Patter(
    carrier=Twilio(),
    phone_number="+15550001234",
    webhook_url="api.example.com",          # stable hostname — not a tunnel
)

tool = PatterTool(
    phone=phone,
    agent={
        "stt": DeepgramSTT(),
        "llm": AnthropicLLM(),
        "tts": ElevenLabsTTS(voice_id="rachel"),
    },
)

async def main():
    await tool.start()                       # boots phone.serve() once (idempotent)

    result = await tool.execute({
        "to": "+15551234567",
        "goal": "Book a haircut for tomorrow at 3 pm.",
    })
    print(result)                            # PatterToolResult
    # → call_id, status, duration_seconds, cost_usd, transcript, metrics

asyncio.run(main())
```

## Schema exporters

`PatterTool` ships three schema exporters with identical wire shape (parameter JSON-schema + result envelope), so a customer can switch frameworks without re-mapping fields.

```python
tool.openai_schema()      # { "type": "function", "function": { "name", "description", "parameters" } }
tool.anthropic_schema()   # { "name", "description", "input_schema" }
tool.hermes_schema()      # { "name", "description", "parameters" }   # same shape as Anthropic input_schema
```

For [Hermes Agent](https://hermes-agent.nousresearch.com), there's a one-line registry helper:

```python
tool.register_hermes(registry, toolset="patter")
```

## Tool arguments (JSON-schema)

Identical across all three exporters:

| Field | Type | Required | Notes |
|---|---|---|---|
| `to` | string | ✓ | E.164 phone number |
| `goal` | string | — | Becomes the in-call agent's system prompt |
| `first_message` | string | — | First thing the agent speaks on answer |
| `max_duration_sec` | integer | — | Hard timeout, default `180`, max `1800` |

## Result envelope

```python
@dataclass(frozen=True)
class PatterToolResult:
    call_id: str
    status: str                  # "completed" | "no-answer" | "busy" | "failed" | "timeout"
    duration_seconds: float
    cost_usd: float
    transcript: list[dict]       # [{"role": "agent" | "user", "text": "..."}]
    metrics: dict                # { p95_latency_ms, cost: { stt, tts, llm, telephony } }
```

When the model framework expects a JSON string (e.g. Hermes), use `tool.hermes_handler()` — it returns exactly that and wraps errors as `{"error": "..."}`.

## Concurrency & timeouts

- `start()` is idempotent — call it from your bootstrap, or let `execute()` boot it lazily.
- Concurrent `execute()` calls are serialised through an `asyncio.Lock` so the per-`call_id` Future never races.
- `max_duration_sec` is enforced by `asyncio.wait_for`; on timeout the tool hangs up the call and returns `status="timeout"`.

## Production deployment

`PatterTool` boots `phone.serve()` once. Make sure the `Patter` instance was constructed with a **stable webhook hostname** in `webhook_url` — never a tunnel — because Twilio/Telnyx need a long-lived HTTPS URL.

For local development, use `tunnel=True` on the `Patter` constructor and let it spawn a cloudflared. For production deploys (Fly.io / Cloud Run / Fargate behind ALB), set `webhook_url` to your public hostname and disable the tunnel.

## Examples

Runnable integration examples live in their own repo (see the [Patter examples organization](https://github.com/PatterAI)):

- `hermes_phone_tool.py` — drop-in `tools/patter.py` for Hermes Agent.
- `openai_assistant_phone_tool.ts` — minimal OpenAI Assistants run dispatching the tool.
- Pattern A vs Pattern B decision matrix in the example repo's README.

{/* TODO: replace with the canonical URL once the integrations example repo is published (e.g. https://github.com/PatterAI/patter-integrations). */}

## What's next

<CardGroup cols={2}>
  <Card title="Tools" icon="wrench" href="/python-sdk/tools">In-call tool calling (function calling within a Patter agent).</Card>
  <Card title="Agents" icon="robot" href="/python-sdk/agents">Configure the in-call agent that PatterTool wraps.</Card>
</CardGroup>
