| name | integrate-openclaw |
| description | Wire Patter as the voice layer on top of an OpenClaw brain — the "brain on the line" pattern. Use when the user runs OpenClaw (or any OpenAI-compatible agent gateway) and wants a phone number that answers any caller, talks with low latency, and consults a specific OpenClaw receptionist agent for real data/actions mid-call without the call dropping during a slow (30-60 s) tool. Covers both directions (OpenClaw drives Patter via `patter-mcp`; Patter consults OpenClaw via `ConsultConfig`), long-tool-call survival, speakerphone noise tuning, open inbound, and least-privilege agent scoping. Patter 0.7.0, Python and TypeScript.
|
| license | MIT |
| compatibility | Requires Patter >= 0.7.0 (`ConsultConfig` / `consult` shipped in 0.6.x) and a running OpenClaw gateway you control. Consult is injected in Realtime and Pipeline modes only (ElevenLabs ConvAI hosts its own tools, so consult does not apply there). For Direction A you also need the `patter-mcp` server from a local clone (it is NOT published to npm).
|
| metadata | {"author":"patter","version":"0.1.0","parity":"both"} |
Integrate Patter with OpenClaw (brain on the line)
OpenClaw is a self-hosted gateway of LLM-backed agents
with scoped tool connections (calendars, customer DBs, …). Patter is the voice
layer: it owns the carrier leg, talks with low latency, and reaches into
OpenClaw only when a turn needs real data or an action. This is the
brain-on-the-line architecture — the inverse of wiring OpenClaw in as a
"Custom LLM" that sits on every turn (the topology that kills calls on slow
tools).
CALLER (any number, no allowlist)
│
▼
Twilio / Telnyx DID ──► PATTER voice agent (OpenAI Realtime, low-latency EN)
│ owns turn-taking + TTS locally
│ speaks "let me check, one moment"
▼
consult ──► local adapter ──► OpenClaw
▲ (loopback) /v1/chat/completions
│ model="openclaw/<receptionistAgentId>"
speaks the reply (ONE scoped agent)
The two directions — and which one to use
| Direction | Who initiates | Transport | Use when |
|---|
| A — OpenClaw drives Patter | OpenClaw agent | patter-mcp MCP server (tools) | OpenClaw should place outbound calls ("call the clinic, wait, tell me what they said"). Blocking until the call ends is the desired semantics here. |
| B — Patter consults OpenClaw | In-call Patter agent | ConsultConfig → HTTP adapter → OpenClaw gateway | Patter answers an inbound call and needs the brain mid-conversation. This is the brain-on-the-line pattern the after-hours-receptionist use case needs. |
They compose, but for an inbound receptionist line Direction B is the one to
build first. The rest of this skill is mostly Direction B; Direction A is at
the end.
Why not OpenClaw's native voice plugin?
OpenClaw's built-in voice plugin only accepts inbound calls from an
allowlist (inboundPolicy: "allowlist" + allowFrom: [...]); there is no
"accept anyone" mode. That is useless for a public after-hours line where random
people call. Patter owns the DID and answers everyone, then reaches the
receptionist over OpenClaw's operator-side /v1/chat/completions gateway — the
caller is never an OpenClaw "sender" subject to the allowlist. This is the single
clearest reason to put Patter in front.
Direction B — Patter consults OpenClaw mid-call
Step 1 — Enable OpenClaw's chat-completions gateway
OpenClaw exposes an OpenAI-compatible POST /v1/chat/completions. It is
disabled by default. Enable it in ~/.openclaw/openclaw.json (JSON5):
{
gateway: {
http: {
endpoints: {
chatCompletions: {
enabled: true,
},
},
},
},
}
**The gateway credential is operator-grade — the `model` field is NOT a security
boundary.** Selecting an agent in the `model` field picks the *persona*, but with
shared-secret (token/password) auth the gateway credential carries full operator
scope across the whole gateway. Real isolation comes from three things, none of
which is the token:
- A dedicated least-privileged receptionist agent with a tight per-agent
tools.allow / tools.deny (see Step 5).
- One gateway per client — bind the gateway to loopback / tailnet only
and never expose
/v1/chat/completions to the public internet. On a single
always-on Mac Mini per client (one OS user = one gateway = one credential
set), this isolation is free.
- An auth credential on the gateway (mirror OpenClaw's own gateway-auth
guidance — do not stand up an unauthenticated, network-reachable endpoint).
Confirm the exact gateway bind/auth keys and the model-routing alias forms
against the live OpenClaw docs (docs.openclaw.ai)
before you ship — they are OpenClaw-side config, not Patter's.
Step 2 — Route the consult to ONE specific receptionist agent
On /v1/chat/completions the model field is an agent target:
"openclaw" / "openclaw/default" → the default agent — which may be the
master / CEO / financial agent and can drift between environments. Never use
this from the voice layer.
"openclaw/<receptionistAgentId>" → one explicit, named agent. Always pin
this. (Alias forms openclaw:<id> / agent:<id> are commonly accepted —
verify against OpenClaw docs.)
Per client, set the agentId explicitly:
| Client | Receptionist agentId |
|---|
| Roofing contractor (CA) | openclaw/roofing-ca-receptionist |
| Home-automation contractor (FL) | openclaw/home-fl-receptionist |
Step 3 — Point consult at the OpenClaw agent
Recommended: the native target — no adapter. Patter's consult speaks OpenClaw's
/chat/completions endpoint directly; point it at one scoped agent in a single line:
from getpatter import ConsultConfig, OpenAIRealtime2
agent = phone.agent(
engine=OpenAIRealtime2(),
system_prompt="You are the after-hours receptionist...",
consult=ConsultConfig.openclaw("receptionist"),
)
import { openclawConsult, OpenAIRealtime2 } from "getpatter";
const agent = phone.agent({
engine: new OpenAIRealtime2(),
systemPrompt: "You are the after-hours receptionist...",
consult: openclawConsult("receptionist"),
});
It targets model="openclaw/receptionist", sends the call id as the OpenAI user field
plus the x-openclaw-session-key header (one OpenClaw session per call), reads the
operator-grade bearer from OPENCLAW_API_KEY (never logged), auto-enables allow_loopback
for the co-located gateway, and attaches a default "let me check" reassurance filler.
Notify OpenClaw at call end with openclaw_post_call_notifier("receptionist") /
openclawPostCallNotifier("receptionist") on serve(on_call_end=...) to post the call
record (caller, line, duration, transcript) to the same agent and session.
Escape hatch — a hand-written adapter, only when you need a custom request/response
mapping (e.g. a non-OpenClaw back office). Patter POSTs { request, call_id, caller, callee }; the adapter must:
- Target one named agent (
model="openclaw/<receptionistAgentId>").
- Carry
call_id as the OpenClaw session key so a multi-turn call maps to
one OpenClaw session (continuity), and forward caller for caller-ID
prefetch of the customer record.
- Return a concise, spoken-ready string (don't dump a raw API blob back
into the voice context).
import os
import httpx
from fastapi import FastAPI, Request
app = FastAPI()
OPENCLAW_URL = os.environ["OPENCLAW_URL"]
OPENCLAW_TOKEN = os.environ["OPENCLAW_GATEWAY_TOKEN"]
RECEPTIONIST_AGENT = os.environ.get("OPENCLAW_AGENT", "openclaw/receptionist")
@app.post("/consult")
async def consult(req: Request):
body = await req.json()
request_text = body.get("request", "")
call_id = body.get("call_id", "")
caller = body.get("caller", "")
payload = {
"model": RECEPTIONIST_AGENT,
"user": call_id,
"messages": [{
"role": "user",
"content": f"[caller={caller}] {request_text}",
}],
}
headers = {"Authorization": f"Bearer {OPENCLAW_TOKEN}"}
async with httpx.AsyncClient(timeout=75.0) as client:
resp = await client.post(OPENCLAW_URL, json=payload, headers=headers)
resp.raise_for_status()
content = resp.json()["choices"][0]["message"]["content"]
return {"reply": content.strip()[:600]}
import express from "express";
const app = express();
app.use(express.json());
const OPENCLAW_URL = process.env.OPENCLAW_URL!;
const OPENCLAW_TOKEN = process.env.OPENCLAW_GATEWAY_TOKEN!;
const RECEPTIONIST_AGENT = process.env.OPENCLAW_AGENT ?? "openclaw/receptionist";
app.post("/consult", async (req, res) => {
const { request = "", call_id = "", caller = "" } = req.body ?? {};
const payload = {
model: RECEPTIONIST_AGENT,
user: call_id,
messages: [{ role: "user", content: `[caller=${caller}] ${request}` }],
};
const resp = await fetch(OPENCLAW_URL, {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${OPENCLAW_TOKEN}` },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(75_000),
});
const data = await resp.json();
const content: string = data.choices[0].message.content;
res.json({ reply: content.trim().slice(0, 600) });
});
app.listen(8000);
The adapter is also where you can drop slow work onto an OpenClaw sub-agent
(e.g. spawn a worker, return a short "checking…" and let a later turn fetch the
result). Optional — the in-band reassurance below already covers dead air for
a single 60 s lookup.
Step 4 — Point the Patter agent at the adapter
ConsultConfig (Python) / consult (TS) auto-injects a consult_agent tool the
in-call agent can call. On a single Mac Mini the adapter is on loopback, so opt
in with allow_loopback / allowLoopback.
from getpatter import Patter, Twilio, OpenAIRealtime2, ConsultConfig
phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
engine=OpenAIRealtime2(),
system_prompt=(
"You are the after-hours receptionist for Acme Roofing. "
"When the caller asks about appointments, availability, or account "
"details, call `consult_agent` with a clear request. "
"Say a brief 'let me check' first, then read back the answer."
),
first_message="Thanks for calling Acme Roofing, how can I help?",
consult=ConsultConfig(
url="http://127.0.0.1:8000/consult",
timeout_s=75.0,
allow_loopback=True,
headers={"Authorization": "Bearer <adapter-token>"},
),
)
phone.serve(agent)
import { Patter, Twilio, OpenAIRealtime2 } from "getpatter";
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
const agent = phone.agent({
engine: new OpenAIRealtime2(),
systemPrompt:
"You are the after-hours receptionist for Acme Roofing. " +
"When the caller asks about appointments, availability, or account " +
"details, call consult_agent with a clear request. " +
"Say a brief 'let me check' first, then read back the answer.",
firstMessage: "Thanks for calling Acme Roofing, how can I help?",
consult: {
url: "http://127.0.0.1:8000/consult",
timeoutMs: 75_000,
allowLoopback: true,
headers: { Authorization: "Bearer <adapter-token>" },
},
});
await phone.serve({ agent });
Defaults to know: consult timeout defaults to 30 s (timeout_s=30.0 /
timeoutMs: 30000), the tool is named consult_agent, and consult is injected
in Realtime and Pipeline only (a warning is emitted if you set it with
ElevenLabs ConvAI).
Long-tool-call survival (the literal blocker)
The prior failure with another platform was: the agent makes a 30-60 s tool call
(sometimes browser automation) and the call dies. The fix is three layers,
all of which must hold.
1. The timeout ladder — the call dies at the SHORTEST link
| Layer | Where | Set to | Why |
|---|
| Patter consult timeout | ConsultConfig.timeout_s / consult.timeoutMs | 75-90 s | Above the brain's worst-case tool time. Default 30 s is too short for 30-60 s automation. |
| Adapter HTTP timeout | httpx.AsyncClient(timeout=…) / AbortSignal.timeout(…) | = consult timeout | The adapter must outlast the consult, not undercut it. |
OpenClaw gateway chatCompletions timeout | OpenClaw config | raised | The gateway must not give up before the agent's tool returns. |
Direction A: mcp.servers.patter.timeout | OpenClaw config | 600 s | A blocking make_call runs for the whole call. |
Verify the per-server timeout is honoured. OpenClaw's docs don't publish a
default for the per-server MCP timeout, and an MCP client can impose its own
read-timeout ceiling that silently caps a long blocking call regardless of config.
After wiring, probe the server with openclaw mcp doctor patter --probe (a live
connection check). openclaw mcp status --verbose shows the resolved timeout but
does NOT open a connection.
2. Reassurance — speak a filler the instant the tool starts
Without a filler the caller hears dead air for the whole lookup and assumes the
line froze. Attach reassurance to the consult tool (or any slow tool) so
the agent speaks immediately and keeps the media stream alive while the brain
works.
The consult_agent tool is auto-built, so to give it reassurance the cleanest
path today is to add your own slow tool that wraps the consult, OR steer the
agent via the system prompt to say "let me check, one moment" before it calls
consult_agent. For a hand-built slow tool, set reassurance on the Tool
(Python) / ToolDefinition (TS):
from getpatter import Tool
check_schedule = Tool(
name="check_schedule",
description="Look up the caller's appointment in the calendar.",
parameters={"type": "object", "properties": {"day": {"type": "string"}}, "required": ["day"]},
handler=my_slow_handler,
reassurance="Let me check the calendar for you, one moment.",
)
const checkSchedule = {
name: "check_schedule",
description: "Look up the caller's appointment in the calendar.",
parameters: {
type: "object",
properties: { day: { type: "string" } },
required: ["day"],
},
handler: mySlowHandler,
reassurance: "Let me check the calendar for you, one moment.",
timeoutMs: 60_000,
};
**Reassurance is Realtime-only as of 0.7.0.** In Pipeline mode the field is silently
ignored (the LLM has to generate its own filler); ConvAI doesn't expose it. For
the receptionist line, **use Realtime** — it is also the lowest-latency English
path and has the strongest tool flow.
3. Per-tool timeout
- TypeScript:
ToolDefinition.timeoutMs (default 10 000 ms, clamped to
300 000 ms). Raise it to 60_000 for slow tools — shown above. A timeout
returns { error, fallback: true } and is not retried.
- Python:
timeout_s on the tool() factory / Tool dataclass since
0.6.4 (tool(name=..., handler=..., timeout_s=60.0); default None keeps
the 10 s behaviour, clamped to 300 s). Same terminal semantics as TS. The
consult path keeps its own independent ConsultConfig.timeout_s.
Why this beats "OpenClaw as a Custom LLM"
The failure mode to avoid is wiring OpenClaw in as the per-turn LLM (a
"Custom LLM" / /v1/chat/completions on the critical path of every utterance).
Then the brain's full reasoning + tool latency blocks every response, and a
60 s tool stalls the turn with no acknowledge-then-continue primitive. Patter's
consult is the inverse: the local voice model owns turn-taking and TTS, and
the brain is a consulted specialist invoked only on hard turns. Keep it that way.
Noise & turn-detection for speakerphone
Contractors call from job sites on speakerphone. Tiny noises (a mouse move, the
phone shifting) can false-trigger turn detection and cut the agent off
mid-sentence. The live levers:
| Lever | Field | Mode | For the noisy line |
|---|
| Barge-in floor | barge_in_threshold_ms / bargeInThresholdMs (default 300) | Realtime + Pipeline | Raise to ~500 so a transient doesn't count as a barge-in. 0 disables barge-in entirely. |
| Far-field noise reduction (0.6.4+) | openai_realtime_noise_reduction="far_field" / openaiRealtimeNoiseReduction | Realtime | Recommended for speakerphone / conference-room callers. |
| Server-VAD tuning (0.6.4+) | realtime_turn_detection=RealtimeTurnDetection(...) / realtimeTurnDetection | Realtime | Raise threshold (~0.7) and silence_duration_ms (~700), or switch to type="semantic_vad" with eagerness="low" so callers can finish a thought. |
| Krisp denoiser (SDK main, post-0.7.0) | denoiser="krisp-viva-tel-v2" (job-site noise) or "krisp-bvc-o-pro-v3" (background voices) | Pipeline | BYO license: getpatter[krisp] + KRISP_VIVA_SDK_LICENSE_KEY + KRISP_MODELS_DIR. |
| High-pass + AGC (0.7.0) | high_pass_hz=100, agc=True / highPassHz, agc | Pipeline | Kills mains hum / handling rumble; levels quiet variable-distance talkers. |
| Silero VAD | Agent(vad=SileroVAD.load(...)) | Pipeline | Raise activation to ~0.8 (filters background noise) and silence to ~0.6-1.0 s (lets callers pause mid-thought). |
| Semantic end-of-turn | turn_detector=SmartTurnDetector.load() (audio) or NamoTurnDetector.load() (transcript; SDK main, post-0.7.0) | Pipeline | Holds the turn while the caller is mid-sentence, bounded by max_semantic_hold_ms (default 1200). |
Realtime receptionist (reassurance works here — preferred for this line):
from getpatter import Patter, Twilio, OpenAIRealtime2, RealtimeTurnDetection
phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
engine=OpenAIRealtime2(),
system_prompt="You are the after-hours receptionist for Acme Roofing.",
first_message="Thanks for calling Acme Roofing, how can I help?",
barge_in_threshold_ms=500,
openai_realtime_noise_reduction="far_field",
realtime_turn_detection=RealtimeTurnDetection(
type="server_vad", threshold=0.7, silence_duration_ms=700,
),
consult=ConsultConfig(url="http://127.0.0.1:8000/consult", timeout_s=75.0, allow_loopback=True),
)
import { Patter, Twilio, OpenAIRealtime2 } from "getpatter";
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15550001234" });
const agent = phone.agent({
engine: new OpenAIRealtime2(),
systemPrompt: "You are the after-hours receptionist for Acme Roofing.",
firstMessage: "Thanks for calling Acme Roofing, how can I help?",
bargeInThresholdMs: 500,
openaiRealtimeNoiseReduction: "far_field",
realtimeTurnDetection: { type: "server_vad", threshold: 0.7, silenceDurationMs: 700 },
consult: { url: "http://127.0.0.1:8000/consult", timeoutMs: 75_000, allowLoopback: true },
});
Pipeline variant — deeper audio chain when you control the STT/LLM/TTS stack
(see build-voice-agent → references/pipeline-mode.md for the full lever
docs):
from getpatter import Patter, Twilio, NamoTurnDetector
from getpatter.providers.silero_vad import SileroVAD
phone = Patter(carrier=Twilio(), phone_number="+15550001234")
agent = phone.agent(
system_prompt="You are the after-hours receptionist for Acme Roofing.",
first_message="Thanks for calling Acme Roofing, how can I help?",
barge_in_threshold_ms=500,
denoiser="krisp-viva-tel-v2",
high_pass_hz=100,
agc=True,
vad=SileroVAD.load(activation_threshold=0.8, min_silence_duration=0.7),
turn_detector=NamoTurnDetector.load(),
consult=ConsultConfig(url="http://127.0.0.1:8000/consult", timeout_s=75.0, allow_loopback=True),
)
**Don't reach for `echo_cancellation` on a phone line.** Patter's AEC is
browser/native-audio only — on a PSTN call the carrier round-trip exceeds the
filter window, so it passes audio through unchanged (line echo is the
carrier's job, ITU-T G.168). For speakerphone TTS bleed use the Realtime
`far_field` preset or the Pipeline denoiser instead. Reassurance is still
Realtime-only — prefer **Realtime** for the receptionist line, which since
0.6.4 also has first-class noise levers (`far_field`,
`realtime_turn_detection`).
Inbound: anyone can call
Patter answers any caller on its DID — no allowlist. There are two ways to
set the inbound agent:
- In the SDK: the agent you pass to
phone.serve(agent) answers all inbound
calls. Verify X-Twilio-Signature / Telnyx Ed25519 at the carrier edge
(Patter does this for you) — that is the only trust boundary the public touches.
- Via
patter-mcp (Direction A): the configure_inbound tool sets the
default agent that answers all future inbound calls. Fields: systemPrompt,
firstMessage (opt.), engineMode (default pipeline), sttProvider /
llmProvider / ttsProvider (pipeline), voice, language.
Contrast with OpenClaw's native voice plugin, whose inboundPolicy is
allowlist-only (disabled by default) — it structurally cannot front random after-hours callers.
Patter terminates the carrier leg for everyone; OpenClaw stays the brain
behind it.
Least-privilege agent scoping (OpenClaw side)
The voice layer must reach only the receptionist, never the master agent.
Model the receptionist as a top-level agents.list[] entry (not a sub-agent
of the master — sub-agent auth can fall back to the parent's credentials), with:
- its own workspace /
agentDir / auth profiles,
- a tight
tools.allow (only the calendar + customer-DB tools) and explicit
tools.deny (exec / write / browser / gateway),
- per-agent sandbox (
mode: all, scope: agent).
Expose any third-party MCP tools (calendar, customer DB) only to that agent
(per-server agent projection + a narrow toolFilter.include). Per-client
acceptance gate before going live:
- List agents and their bindings; confirm the receptionist is bound to the
inbound path and the master is not reachable from it.
- Inspect the receptionist's effective tool list — it CAN reach the calendar
/ DB tools, and CANNOT reach exec / financial tools.
- Run OpenClaw's security audit; verify the gateway binds loopback (not public).
- Run one full long-tool-call end to end (a 45-60 s lookup) and confirm the
call survives and the agent speaks the result.
The exact OpenClaw config keys, CLI verbs, and audit commands are OpenClaw-side
— confirm them against docs.openclaw.ai. Patter's
contract is only the consult HTTP body and the named-agent model value.
Direction A — OpenClaw places calls via Patter (patter-mcp)
Use this when OpenClaw should initiate calls. Run the MCP server from a local
clone (it is not on npm) and register it in ~/.openclaw/openclaw.json. Prefer a
long-lived streamable-http server on an always-on box — OpenClaw reaps idle
stdio runtimes, which would re-spawn the embedded Patter server per session.
{
mcp: {
servers: {
patter: {
url: "http://localhost:3000/mcp",
transport: "streamable-http",
timeout: 600, // blocking make_call runs the whole call
toolFilter: {
// Narrow to the phone path — drop configure_inbound / get_metrics here.
include: ["make_call", "call_third_party", "get_transcript", "get_calls", "end_call"],
},
// Scope this server to the receptionist via the agent's per-agent tool
// policy (agents.list[].tools.allow + sandbox alsoAllow of "bundle-mcp").
// NOTE: mcp.servers.<name>.codex.agents projects ONLY to the Codex
// app-server runtime, NOT embedded-OpenClaw agents — don't rely on it here.
},
},
},
tools: {
sandbox: {
tools: {
// MCP tools live in the "bundle-mcp" group; allowlist it (or "patter__*").
alsoAllow: ["bundle-mcp"],
},
},
},
}
The seven patter-mcp tools: make_call, call_third_party, get_calls,
get_transcript, end_call, get_metrics, configure_inbound. With
wait: true, make_call / call_third_party block until the call ends and
return the outcome plus transcript in one response.
Prefer OAuth / mTLS over static env-var secrets committed in the config, and
verify reachability after wiring (openclaw mcp doctor patter --probe).
Gotchas
- Never set
model: "openclaw" / "openclaw/default" from the voice layer.
It resolves to the default agent (possibly the master) and drifts between
environments. Always pin "openclaw/<receptionistAgentId>".
- The gateway credential is operator-grade. The
model field selects a
persona, not a permission scope. Isolation = least-privileged agent + loopback
binding + one-gateway-per-client, not the token.
- Consult timeout defaults to 30 s — too short for 30-60 s automation. Set
timeout_s / timeoutMs to 75-90 s AND raise the OpenClaw gateway timeout.
The call dies at the shortest link.
- Reassurance is Realtime-only. Pipeline ignores it. Prefer Realtime for the
receptionist line so the "let me check" filler actually plays.
- Raise the per-tool timeout on slow tools — both SDKs default to 10 s.
Since 0.6.4: Python
tool(..., timeout_s=60.0), TS
ToolDefinition.timeoutMs: 60_000 (both clamped to 300 s; a timeout is
terminal, not retried).
allow_loopback is the intended shape here, not a hack — on a co-located
Mac Mini the adapter and gateway are on loopback. It relaxes only the consult
URL's host check; non-HTTP(S) schemes are still rejected.
- The adapter must forward
call_id / caller. The default doc adapter
drops them; without user=call_id every consult starts a fresh OpenClaw
session (no continuity) and caller-ID prefetch is impossible.
echo_cancellation is a no-op on PSTN calls — Patter's AEC only applies
to browser/native audio; the carrier handles line echo. The real noise
levers are openai_realtime_noise_reduction="far_field" +
realtime_turn_detection (Realtime, 0.6.4+) and denoiser /
high_pass_hz / agc / Silero VAD / turn_detector (Pipeline).
patter-mcp is not on npm. Run it from a local clone of
PatterAI/patter-mcp.
Common errors
| Symptom | Fix |
|---|
| Call drops ~30 s into a tool/lookup | Consult/adapter timeout still at the 30 s default, or the OpenClaw gateway/MCP timeout is lower. Raise them to cover the work and probe with openclaw mcp doctor --probe to confirm the per-server timeout is honoured. |
| Caller hears dead air during a lookup | No reassurance firing. Use Realtime mode and either set reassurance on a slow tool or prompt the agent to say "let me check" before calling consult_agent. |
| Consult reaches the wrong / a privileged agent | Adapter sends model: "openclaw". Pin "openclaw/<receptionistAgentId>" and lock the agent's tools.allow/deny. |
ValueError: ConsultConfig url must be http(s) / host rejected | Loopback/private host with the SSRF guard on. Set allow_loopback=True / allowLoopback: true for your local adapter URL. |
| Agent cut off mid-sentence on a noisy line | Raise barge_in_threshold_ms to ~500. In Realtime add openai_realtime_noise_reduction="far_field" and raise the realtime_turn_detection threshold; in Pipeline set a denoiser and raise the Silero activation_threshold. |
| Multi-turn call has no memory of earlier turns | Adapter not sending user=call_id — every consult opens a new OpenClaw session. |
| OpenClaw agent can't see Patter's MCP tools (Direction A) | bundle-mcp not allowlisted in tools.sandbox.tools.alsoAllow, or timeout too low for a blocking call. |
| Inbound calls from unknown numbers rejected | You're using OpenClaw's native voice plugin (allowlist-only). Put Patter on the DID instead; it answers everyone. |
Related skills
References