agent_id. Calls that
land on the trunk inherit the DAG.
DAG shape
A DAG is a JSON document. On the wire, the gateway treats the body as an opaquestring. The server validates the body after decode.
The shape is:
node_type values today:
Node types are case-sensitive and uppercase. New types are additive.
Older runtimes ignore new types (forward-compat).
Conversational nodes
IVR nodes
GREETING plays an opening message. GATHER waits for DTMF input.
MENU routes to one of N downstream nodes, one per digit (for example,
“press 1 for sales”).
Tools available to LLM nodes
Anllm node’s args.tools is a list of tool names. Names resolve
through the runtime’s ToolRegistry. The LLM sees the tool’s name,
description, and JSON schema. The runtime executes the tool and sends
the result back as a tool_result.
Built-in implicit telephony toolset
Every session in ClutchCall’s agent runtime automatically advertises these tools to the LLM:
To hide some of these tools for a tenant, write the tool names to
clutchcall:tenant:<tenant_id>:telephony_disabled_tools as a
JSON array. The runtime filters out disabled tools before the toolset
reaches the model. The runtime also checks the list again at invoke
time, so a stale provider session cannot bypass the gate.
Realm policy on transfer_call. Each trunk carries a list of
transfer rules. The rules are cached in
clutchcall:trunk:<trunk_id>:transfer_rules. The runtime walks
the rules on every transfer attempt. The first match wins.
When no rule matches:
- The runtime allows same-realm transfers (
reason="same_realm_default"). - The runtime soft-allows cross-realm transfers with a warning
(
reason="cross_realm_default_warn"). It logs the transfer and records it in the CDR, but the transfer proceeds. To enforce strict default-deny, write an explicit catch-alldenyrule. The soft default exists so that operators who did not write rules yet can still make cross-realm transfers.
action: "deny" returns a structured
envelope to the LLM:
Operator-defined tools (HTTP / MCP / Client)
Operators add per-agent tools through the portal. They do not rebuild the runtime. The runtime persists tools in Redis atagent:<agent_id>:config.tools[] and hydrates them on the first audio
frame of each new session. These tools share the per-session
ToolRegistry with the implicit toolset above. When names collide, the
operator-defined tool wins. For example, a transfer_call declared on
the agent overrides the built-in transfer_call.
Per-tool spec shape (one entry of
tools[]):
Native tools (runtime build)
Some tools need privileged access to the runtime: realm policy evaluation, telephony RPC dispatch, recording control, and supervisor signalling. For these tools, subclasshost::core::Tool and call
register_tool() in main.cc. This path is for platform developers.
Tenants and operators should use the http / mcp kinds above. See
the clutchcall-tool-calling skill for the C++ shape and the
register_tool call site.
Client-side tool calling
When you bridge the call yourself (default_app=ANSWER), you handle
tool calling directly with the LLM provider’s wire format. For OpenAI
Realtime, you send a tools array in session.update. The provider
sends response.function_call_arguments.done events back. The
clutchcall-tool-calling skill shows the full round-trip.
PublishAgentDag
Request (PublishAgentDagRequest):
Response (
PublishAgentDagResponse):
Each publish creates a new version. The gateway keeps the last 50
versions by default. New calls bind to the latest version at the moment
of
dial.
GetAgentDag
Response (
GetAgentDagResponse):
ListAgentDags
Response (
ListAgentDagsResponse):
AgentSummary:
DeleteAgentDag
Returns
Empty. Trunks that point at the deleted agent_id will fail
inbound HANDLE_AI routing with ERR_INVALID_DESTINATION. This
continues until you rebind the trunks to another agent or republish
the DAG.
