Recipes← all recipesView on GitHub

Pause a voice AI agent mid-call and bring it back

AI AgentsAI agent hold and resume

Put the caller on hold with a spoken line, take them off it, or end the AI on a call that keeps running, each with one REST command.

rest

The claim

An agent waiting on something slow has two bad options. It can talk through the wait, or sit silent while the model tries to fill the gap. This is the third: your backend holds the caller, does the work, and takes them off hold.

Why it holds

The vendored REST spec, tools/openapi/rest.json, is the authority.

  • calling.ai_hold takes a prompt, which the spec describes as “a system message added to the AI conversation before placing the caller on hold. The AI will speak this message to the caller before hold music begins.“ It also takes a timeout.
  • calling.ai_unhold takes no params at all.
  • calling.ai.stop documents one param, control_id, and calls it “Reserved field … currently ignored”, so this recipe does not send it.

*The timeout is a string.* The spec types it as one and says so twice: “must be sent as a string” and “integer payloads are rejected”. A number here is a 400, so the recipe converts, and refuses a fractional value rather than rounding one silently.

How it works

def hold(call_id, seconds=90, prompt=HOLD_PROMPT):
    if int(seconds) != float(seconds):
        raise ValueError(...)
    return client.calling.ai_hold(call_id, timeout=str(int(seconds)), prompt=prompt)

def stop(call_id):
    # the method is ai_stop; the command on the wire is calling.ai.stop
    return client.calling.ai_stop(call_id)

What the platform receives for hold:

{"command": "calling.ai_hold",
 "id": "6d3f4a0e-2b1c-4e7a-9f0d-1c2b3a4d5e6f",
 "params": {"timeout": "90",
            "prompt": "Let me check that for you. One moment."}}

The method name and the command are not the same word. ai_stop() sends calling.ai.stop, with a dot, in both SDKs. That is the usual rule here: the key the platform receives is what a verifier asserts, never the name of the method that produced it.

Limitations

The verifier proves the requests, not the hold. What the caller hears while held, and how the agent picks the conversation back up, are live behaviour.

calling.ai.stop ends the AI, not the call. Whatever is left in the document after the ai verb is what happens next. On a document that was only an agent, that is the end of the call.

What to change first

Pass timeout=90 instead of timeout=str(90) in hold and run the verifier. The string assertion fails, which is the point: this is the one param in the recipe where the JSON type is the whole rule.