An agent DAG is a directed-acyclic graph. It tells the gateway how to drive an AI conversation: which model to call, which tools the model can use, which fallback prompts to play, and which conditions start a hand-off to a human. Trunks bind to a DAG by agent_id. Calls that land on the trunk inherit the DAG.
There is no SDK wrapper. Use the raw RPC envelope to call these methods.

DAG shape

A DAG is a JSON document. On the wire, the gateway treats the body as an opaque string. The server validates the body after decode. The shape is:
The runtime recognises these 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

An llm 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-all deny rule. The soft default exists so that operators who did not write rules yet can still make cross-realm transfers.
A rule that matches with action: "deny" returns a structured envelope to the LLM:
The model can then fall back, pick a different destination, or explain the limitation to the caller. The downstream gateway never sees the denied transfer.

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 at agent:<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[]):
HTTP and MCP tool invokes are synchronous within the LLM turn. A slow upstream uses the turn budget. Keep each tool fast (less than one second is ideal). As an alternative, chain multiple LLM turns instead of a cascade of tool calls inside one turn.

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, subclass host::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.