Skip to main content
Hermes Agent is a self-hosted AI assistant that grows with you through modular skills and persistent memory. If you already run Hermes, Patter adds phone calling in three directions:
  • Direction A — Patter is the voice shell, Hermes is the brain. Patter answers the phone — carrier, speech-to-text, turn-taking, barge-in, text-to-speech — and routes every conversation turn to your Hermes agent as the LLM, using the new HermesLLM pipeline provider. This is the headline integration; the rest of this page covers the other two directions.
  • Direction B — Hermes places calls. Connect Patter as an MCP server and Hermes gains tools to dial out, read transcripts, end calls, and inspect metrics.
  • Direction C — Patter consults Hermes on demand. During a live call, a Patter agent can reach back to Hermes for deeper reasoning, fresh information, or a decision the in-call prompt can’t make on its own.
Pick one direction or wire up several — they are independent.

What if Hermes could pick up the phone?

You already have a Hermes agent with memory, skills, and tools. Direction A puts it on the end of a phone line: a caller dials your number, and they are talking to Hermes — not to a separate voice bot that occasionally asks Hermes for help. Patter is the voice shell: it owns everything that has to happen in real time (the carrier leg, transcribing the caller, deciding when a turn ends, handling barge-in, and speaking the reply), and for the one thing that requires intelligence — what to say next — it calls your Hermes agent. This is the same shape as wiring a custom LLM into a hosted voice platform, except you own the voice layer and run it next to Hermes. Because Hermes is reached over its OpenAI-compatible HTTP API (POST /v1/chat/completions), the new HermesLLM provider plugs straight into Patter’s pipeline mode as the LLM stage.

Architecture

Hermes runtimes execute tools, recall memory, and run skills before they answer, so a single turn can take 30–90 s. That is why HermesLLM defaults to a 120 s request timeout (the generic provider’s 60 s, raised for the preset) instead of the short ceiling used for raw inference providers — a turn that runs a tool isn’t cut off mid-thought. Because a tool-running turn can leave the caller in silence for several seconds, the agent supports an opt-in spoken filler: set long_turn_message / longTurnMessage (with long_turn_message_after_s / longTurnMessageAfterS, default 4 s) and Patter speaks a short line if no audio has reached the caller yet by then. It fires once per turn, only on slowness, and never overlaps the real reply. (A separate llm_error_message / llmErrorMessage covers the gateway-down / timeout error case.)
Where the session lives. Hermes is stateless and keys continuity off HTTP headers, not the OpenAI user field. Each phone call maps to one Hermes session: Patter sends X-Hermes-Session-Id: patter-call-<call_id> on every turn, so multi-turn session and transcript continuity inside a call works without any extra wiring. This is on by default for HermesLLM.For long-term memory scoping across calls, set session_key (Python) / sessionKey (TypeScript) on HermesLLM — Patter then also sends X-Hermes-Session-Key: <your value>, which tells Hermes which memory scope this caller belongs to. It is off by default (no header sent) and is opt-in:
For per-caller memory without storing the raw phone number, derive the key from a caller hash instead of a static value — HermesLLM(session_key_from="caller_hash") / new HermesLLM({ sessionKeyFrom: 'caller_hash' }) emits X-Hermes-Session-Key: patter-caller-<hash> (SHA-256, 16 hex chars), so Hermes remembers a caller across calls while the raw number never reaches the wire or the logs. For a custom scheme, pass session_key_factory / sessionKeyFactory, a callback that receives a SessionContext (call_id / caller / callee / caller_hash) and returns the scope value (a falsy return omits the header for that call).(Patter also still sends user=patter-call-<call_id> for upstream-log correlation, but that field is not what drives the Hermes session — the headers are.)

Prerequisites for the voice shell

You also need:
  • A running Hermes agent runtime with its OpenAI-compatible API server enabled (next section).
  • A phone number from Twilio or Telnyx, and the matching carrier credentials.
  • An STT and a TTS provider for the pipeline (e.g. Deepgram STT + ElevenLabs TTS). Patter handles the audio; Hermes only ever sees text.

Setting up the Hermes gateway

Hermes exposes an OpenAI-compatible HTTP API. Enable it and bind it to loopback so only processes on the same machine — including Patter — can reach it:
Start the gateway (per your Hermes install), then confirm it answers on loopback before wiring Patter to it:
A JSON list of models (including your hermes-agent) means the gateway is up. If curl hangs or refuses the connection, fix that before going further — Patter can’t reach a gateway that isn’t listening.
HermesLLM reads these env vars for you. With API_SERVER_KEY and (optionally) API_SERVER_MODEL_NAME set, you can construct HermesLLM() with no arguments — it defaults base_url to http://127.0.0.1:8642/v1, the api key from API_SERVER_KEY, and the model from API_SERVER_MODEL_NAME (falling back to hermes-agent).

Running Patter locally

Build a pipeline-mode agent whose LLM is HermesLLM. Patter wraps the carrier, STT, and TTS around it; Hermes is the brain for every turn.

Python example

HermesLLM() with no arguments is the common case — every default comes from the env. To target a remote Hermes or pin a model explicitly:

TypeScript example

To target a remote Hermes or pin a model explicitly:

Connecting Twilio or Telnyx

The voice shell answers a real phone number. phone.serve(agent) exposes a webhook that the carrier calls when a number rings; point your number’s voice webhook at Patter’s public URL.
  • Local dev: run Patter behind a tunnel (a stable cloudflared hostname is best — an ephemeral quick-tunnel rotates its URL on every restart) and set that URL as the number’s voice webhook.
  • Twilio: in the Twilio console, set the number’s “A call comes in” webhook to your Patter URL. Patter verifies X-Twilio-Signature on every inbound request.
  • Telnyx: in the Telnyx portal, point the number’s voice connection at your Patter URL. Patter verifies the Ed25519 signature.
Only Patter is exposed to the carrier. Hermes stays on loopback (see Security notes).

Production deployment

For an always-on line, run Patter and Hermes on the same machine and keep the gateway private:
  • Bind the Hermes gateway to 127.0.0.1:8642 (API_SERVER_HOST=127.0.0.1) — Patter reaches it over loopback, so it never needs to be tunnelled or ngrok-exposed.
  • Expose only Patter’s carrier webhook to the internet — via a stable production webhook URL or a persistent cloudflared tunnel with a fixed hostname.
  • Set a strong API_SERVER_KEY even on loopback; defence in depth.
  • Pin the SDK version and your provider/carrier keys via environment variables, never in source.

Security notes

  • Hermes does not need to be on the public internet. When Patter and Hermes run on the same box, Patter reaches Hermes on 127.0.0.1:8642 — only Patter is exposed to the carrier. This is safer than the hosted custom-LLM path, where your brain endpoint has to be publicly reachable for the platform’s cloud to call it. Here the brain stays loopback-only and the only public surface is the carrier webhook, which verifies the carrier signature at the boundary.
  • Use a strong API_SERVER_KEY. Even on loopback, set a real key; HermesLLM sends it as a bearer token and Patter never logs it.
  • Keep the prompt voice-safe. Phone replies are spoken, not read — ask the agent for short, plain sentences and no markdown, lists, or code blocks. Long replies bloat the voice context and slow the next turn.
  • PII and recording consent. Calls may carry personal information; if you record or store transcripts, follow your jurisdiction’s consent rules (two-party-consent states, GDPR, etc.). Treat transcripts as PII and don’t log full caller numbers (Patter logs only the last four digits by default).
Generic OpenAI-compatible runtimes. HermesLLM is a thin preset over the generic OpenAICompatibleLLM provider, which drives any OpenAI-compatible chat endpoint — Hermes, OpenClaw, Ollama, vLLM, LM Studio, or a custom gateway. To point the voice shell at one of those instead, use OpenAICompatibleLLM(base_url=..., model=...) directly. See the OpenClaw page for the OpenClaw preset and the generic-runtime note.

Prerequisites

Patter’s MCP server runs locally — it is not published to npm or PyPI. Clone and build it from PatterAI/patter-mcp:
It can also run over stdio (see Direction A below). Provider and carrier keys are read from the server’s environment: OPENAI_API_KEY, ELEVENLABS_API_KEY, TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TELNYX_API_KEY, and friends. See the patter-mcp README for full setup.

Direction B — Hermes places calls via Patter

Hermes is a native Model Context Protocol client. Register the Patter MCP server under the top-level mcp_servers key in ~/.hermes/config.yaml. The seven Patter tools then surface to Hermes: make_call, call_third_party, get_calls, get_transcript, end_call, get_metrics, and configure_inbound (plus a set of MCP resources).

HTTP recipe

With the server running locally (npm start) on http://localhost:3000/mcp, point Hermes at it:

stdio recipe

Prefer to let Hermes spawn the server itself? Point it at your local build (patter-mcp/dist/index.js from the clone above) with the --stdio flag and pass the provider keys through env:
Raise the timeout for wait:true calls. Hermes defaults its per-server MCP timeout to 120 seconds. A phone call that runs make_call with wait: true blocks until the call ends, which can easily exceed two minutes. Set timeout: 600 (ten minutes) on the patter server, or your long calls get cut off mid-conversation and Hermes never receives the transcript.

Example task

Ask Hermes something like:
“Call +15550001234 and ask whether they have an opening tomorrow morning. Wait for the call to finish, then summarise what they said.”
Hermes calls make_call with wait: true. The tool blocks until the call completes and returns the outcome plus the full transcript in one response, so Hermes can summarise without polling get_transcript separately:

Direction C — Patter consults Hermes on demand

Here the roles are split differently: Patter runs the call with a local in-call agent and only reaches Hermes when that agent decides it needs help — it consults Hermes over HTTP. Use this when most turns are simple and only a few need Hermes’s full reasoning; Direction A (above) routes every turn to Hermes. This is the dispatch + consult pattern described on the Python consult page and the TypeScript consult page — Hermes stays off the per-turn path and is only consulted when the in-call agent decides it needs to.

1. Enable the Hermes API server

Hermes exposes an OpenAI-compatible HTTP API. Enable it with environment variables; it listens on 127.0.0.1:8642 by default:
Hermes then serves POST /v1/chat/completions (and POST /v1/runs for async + SSE), returning the standard OpenAI shape with the answer in choices[0].message.content.

2. Run a tiny adapter

Patter’s consult feature POSTs a body shaped like { "request": "...", "call_id": "...", "caller": "...", "callee": "..." } and reads the reply back from a reply field (it also accepts response / text / result / answer / message). Hermes speaks OpenAI’s chat-completions shape. A roughly 30-line adapter bridges the two: map request into an OpenAI messages array, forward to Hermes, and map choices[0].message.content back to { "reply": ... }.

3. Point Patter’s consult URL at the adapter

When consult is set, Patter auto-injects a consult_agent tool into the agent (Realtime and Pipeline modes). The model calls it with a single request string, the adapter forwards that to Hermes, and the answer is spoken back to the caller.
Loopback / SSRF caveat. By default Patter’s consult URL validator rejects loopback and private hosts (127.0.0.1, localhost, 10.x, 172.16–31.x, 192.168.x, link-local) as an SSRF guard, and the Hermes API server binds to 127.0.0.1:8642. When everything runs on one box, opt in with allow_loopback (Python) / allowLoopback (TypeScript) so the consult URL can point at the local adapter directly. The flag relaxes the loopback / private / link-local host checks for the consult URL only — every other webhook path stays strict, and non-HTTP(S) schemes are still rejected even with the flag on. Only enable it for a URL you control; the consult URL is your own configuration, not caller-derived input.
Prefer to keep the strict default? Expose the adapter on a routable interface or hostname rather than localhost, and keep the adapter — not Hermes — as the public hop. The adapter still reaches Hermes over loopback.

What’s next

  • Read the Concepts page to understand Patter’s voice modes (Realtime end-to-end vs. Pipeline composed STT + LLM + TTS) — the voice shell (Direction A) is a pipeline-mode agent with HermesLLM as the LLM stage.
  • OpenClaw runs the same way — see OpenClaw as the primary LLM for the OpenClawLLM preset and the generic OpenAICompatibleLLM provider (Ollama / vLLM / LM Studio).
  • Consult feature reference: Python · TypeScript.
  • Get a phone number: Twilio console or Telnyx portal.

Need help?