Tools
Let the agent run workflows, call your APIs, use MCP servers, consult other agents, and run a single node during a call.
Tools are the actions an agent can take mid-conversation. Without them an agent can only talk; with them it can look up an account, book an appointment, create a ticket, or check an order — and speak the result back to the caller.
You add tools on the Custom Tools row, in the Tools & Skills group on the editor's Conversation step. The agent decides for itself when to use one, based on the tool's description and what the caller has said. You don't script when a tool fires; you describe what it's for.
The six kinds of tool
Choose a kind in each tool row's first dropdown.
Workflow (Invoke) — runs one of your workflows and returns its result to the agent, which then speaks it. The caller stays with the agent throughout. Use this when the action already exists as a workflow, or when it needs several steps, integrations, or business logic you'd rather build on the canvas than describe to a model.
Workflow (Handoff) — transfers the live call to the workflow. The agent stops speaking and resumes when the workflow finishes. Use this when the workflow needs to talk to the caller itself: collect a card number, play a menu, queue to a person. The two workflow kinds look identical in the picker, and this is the difference that matters at call time.
In a website chat the handoff keeps the visitor in the same panel. In tests the agent can use it, and the test ends without running the workflow.
Integration — calls one of your connected integrations. For a generic integration you define the HTTP request yourself; for a first-class one such as Zendesk, Slack, or Meridian you pick an action from a list instead and Tandem builds the request. See Add an integration tool.
MCP — connects the agent to an MCP integration and exposes the tools that server offers. Unlike the other kinds you don't define a name, description, or parameters: the server publishes its own tools and the agent discovers them when the call starts. Whichever tools you've enabled on that integration are the ones the agent can use.
Agent — consults one of your other agents and speaks with the caller once it answers. Use this to split a broad agent into focused specialists (a billing agent, a scheduling agent) that a single caller-facing agent can call on without duplicating their prompts or tools.
Node — runs a single workflow node directly, without wrapping it in a workflow. Use this for a one-step action (send an email, send a text, log an event) that doesn't need a canvas of its own. See Add a node tool below.
Add a workflow tool
Add the row
Click Add Tool. New rows start as Workflow (Invoke).
Pick the workflow
Choose it from the second dropdown, which lists your workflows.
Name the tool
The Name is what the model calls, not what the caller hears. Use a verb phrase in
snake_case: look_up_order, book_appointment, create_ticket.
Letters, numbers, hyphens, and underscores only, up to 64 characters.
Describe it
The Description is how the agent decides whether this tool is the right one. Write it as the situation it's for, not as a restatement of the name:
Look up the status of a customer's order by order number.
Use this when the caller asks where their order is or when it will arrive.Add the parameters
Click Add Parameter once per value the tool needs, and fill in the row — see Parameters below. A workflow tool's parameters arrive as the workflow's starting variables, named exactly as you name them here.
A tool that needs no values needs no parameters. Leave the list empty.
Save
Click Save Configuration. The tool is available on the agent's next call. On an agent that is on versioning, you'll be editing a draft — the button reads Save as New Draft if you started from the published version — and the tool reaches callers when you publish that draft.
Parameters
Parameters are declared one row at a time, not written as JSON. Each row is a small form:
| Field | What it is |
|---|---|
| Name | The wire name — the {token} in a request path, the query key, the body key, or the header name. For a Model parameter it's also the name the model fills. |
| Type | string, number, integer, or boolean — or Object, which holds nested parameters instead of a value. See Nested request bodies. |
| Location | Integration tools only: path, query, body, or header. See Add an integration tool. |
| Value from | Where the value comes from: Model, Fixed, or Call (below). |
| Description | Model parameters only, and required — it's how the model knows what to put there. |
| Fixed value | Fixed parameters only. |
| Call value | Call parameters only. |
| Required | Whether the model must supply it. A path parameter is always required. |
Value from is the field worth understanding, because it decides what the model can see:
- Model — the agent fills it in from the conversation. This is the only source the model is told about, and the only one it can influence.
- Fixed — a value you type, sent on every call. An API version, a tenant code, a content type. The model never sees it and can't change it.
- Call — a value read from the live call. Pick one of
call.ani(the caller's number),call.id,agent.id, ororg.id. Nothing else is available, and the model can't substitute one.
Fixed and Call parameters are the reason you rarely need to trust the model with anything but
the caller's own words: a lookup keyed on call.ani can't be pointed at somebody else's
account by a caller who asks nicely.
Expand What the model sees under the parameter list to check your work. It shows the exact schema the agent is given — only Model parameters appear in it, with their descriptions and which are required. It's generated by the same code the live call uses, so it can't drift from what the agent actually receives.
A Workflow (Handoff) tool takes Model parameters only. Its arguments ride the transfer itself, so a Fixed or Call value would save and then never reach the workflow. The editor doesn't offer them.
What the agent gets back from a handoff
When the workflow finishes, the caller returns to the same agent, with the conversation from before the transfer still in place. The agent speaks first and does not re-greet: it picks up from the workflow's result, so the caller hears an answer rather than a second hello.
To hand a value back, end the workflow with a Response node. Its JSON object becomes the tool's result, and the agent can read the values out of it:
{ "balance": "$42.00", "dueDate": "2026-10-01" }Without a Response node the agent still resumes and still knows the workflow completed. It just has no data to speak, so it opens by asking what the caller needs next. A workflow that fails partway is reported to the agent as a failed tool call, which is a different thing from one that finished with nothing to return.
One trap: a handoff workflow that ends on a Disconnect node hangs the caller up. There is nobody left to return to, so the agent never resumes. Leave the caller connected and let the workflow run off the end of its path instead.
Handoffs in website chat
Workflow handoffs in chat are being rolled out. Until they are on for your organization, an agent in a website chat isn't offered its Workflow (Handoff) tools. Contact support to turn them on.
In a website chat, a handoff moves the conversation to the workflow without leaving the panel. The workflow's Send Chat Message nodes speak to the visitor, and the visitor's replies go to the workflow until it finishes. The agent then replies straight away from the workflow's result, as it does on a call. It sees the result, not the messages the workflow and the visitor exchanged, so return what it needs from a Response node. A result longer than 4,000 characters is cut off at that length.
A workflow that reaches a Disconnect node while the visitor is in it ends the chat. If the workflow reaches Disconnect after its reply timeout ran out, the chat stays open and the visitor's next message goes back to the agent. Voice nodes such as Play Message and the transfer nodes need a phone call and don't work in a chat; see Answering with a workflow. If the workflow can't start, or stops to wait on something other than the visitor, such as a Time Delay, the agent takes the conversation back and is told the workflow failed. The same happens when the visitor replies after the workflow has stopped running, for example because its reply timeout ran out.
An agent can start at most two workflows from one visitor message. A third handoff in the same message is refused. The agent is told so and answers the visitor in the same response, without handoff tools. A handoff the agent gets wrong, such as a missing required argument, is not started either. The agent is told that too, and answers in the same response.
The chat widget editor's preview doesn't offer handoff tools.
Add an integration tool
The target dropdown lists the integrations your organization has connected, so set one up on the Integrations page first — until you do, the dropdown has nothing to offer. What the row asks for next depends on which one you pick.
Zendesk
Connected Zendesk accounts appear in the dropdown under a Zendesk heading. Picking one replaces the method and path fields with a single Action:
| Action | What the agent does |
|---|---|
| Create Ticket | Files a ticket. The model supplies the subject, the description, the requester's name and email, and optionally a priority, type, and tags. |
| Show Ticket | Reads a ticket back by its number — its status, subject, description, type, priority, tags, and when it was opened and last updated. |
You set the Name and the Description. There is no method, path, or parameter list, because Tandem owns the request: the action decides what the model may send, so it can neither reach an endpoint you didn't choose nor invent a field your Zendesk doesn't have.
Create Ticket adds four optional boxes for where the ticket lands. You fill them in once and every ticket this tool files uses them; the model is never shown them, because an id from your Zendesk is not something it can know.
- Assignee Email — the Zendesk agent to assign, by their sign-in email.
- Assignee ID — the same agent by numeric user id. Zendesk honours this over the email when you set both.
- Group ID — the group to queue the ticket to. Set this alone to hand off to a team rather than a person.
- Brand ID — the brand to file under, on a multi-brand account.
Leave all four blank and your Zendesk account's own triggers route the ticket, as they do today.
A few things the model is deliberately not allowed to fill in: custom fields, External ID, and whether the first comment is visible to the requester. If you need those, use the Create Zendesk Ticket node tool instead and fix them yourself — see Zendesk and Add a node tool.
Slack
Connected Slack apps appear in the dropdown under a Slack heading. Picking one replaces the method and path fields with an Action and a destination:
| Action | What the agent does |
|---|---|
| Send Message | Posts a message. The model writes the text; Slack formatting such as *bold* works. |
Under Post To, choose Channel or Direct Message, then pick the channel or the person.
If it isn't in the list, paste its Slack ID (C0123ABCD for a channel, U0123ABCD for a
person); sending needs only the chat:write scope, not the scopes that fill the list.
Every message this tool sends goes there. The model only writes the text and can't choose where
it goes, because it has no list of your workspace's channels to choose from.
- Channel: the Slack app must be a member. Run
/invite @your-app-namein the channel first. - Direct Message: the app needs the
users:readscope to list people andim:writeto message them.
If you pick a different Slack app on the row, the destination is cleared, because a channel in one workspace doesn't exist in another.
The tool returns the message's ts and channel on success. A Slack rejection, such as the app
not being in the channel, comes back to the model as an error message it can relay. See
Slack for what each one means.
Meridian
Connected Meridian integrations appear in the dropdown under a Meridian heading. Picking one replaces the method and path fields with an Action and a Require caller verification switch. The Meridian integration is being rolled out, so the heading appears only once it is on for your organization.
| Action | What the agent does |
|---|---|
| Find Account | Searches for member accounts by phone, account, member, or meter number, or by address. |
| Get Account | Reads one account's balance and status. |
| Get Outages | Lists the active outages on an account. |
| Report Outage | Reports an outage on an account, with an optional note and callback. |
| Check Arrangement Eligibility | Checks whether an account can set up a payment arrangement. |
You set the Name and the Description. Tandem owns the request, so there is no method, path, or parameter list.
With Require caller verification on (the default), every action except Find Account is refused until the caller has been verified for that account in the conversation. Nothing records a verification yet, so a tool with the switch on can only find accounts, and Find Account returns account numbers only unless it searched by the number the caller is calling from. Turn the switch off to let the tool read and report on any account number the model is given. See Meridian for what each action returns.
Generic integrations
A generic integration tool works like a workflow tool, with one addition: you define the request, so the model never chooses the HTTP method or the URL.
Pick the Integration, then set:
-
Method —
GET,POST,PUT,PATCH, orDELETE. -
Request path — relative to the integration's base URL, and it must start with
/. A placeholder in braces is filled by the parameter of the same name whose Location ispath:/customers/{customerId}/orders
Every parameter on an integration tool needs a Location, and it's explicit. Nothing is inferred from the path or the method:
path— substituted into the request path. The path placeholder and the parameter must have the same name, and the editor offers to create the missing parameter for you when you type the placeholder. Always required.query— appended to the query string.body— sent in the JSON request body. Not allowed on aGET; usequeryinstead.header— sent as a request header. A header can't be filled by the model — use Fixed or Call. Header names are where an API key or token belongs.
To put a credential in a header or body value, reference a secret rather than typing it:
Bearer {{secrets.stripe_key}} in a Fixed value. The reference is resolved on the way
out, so the value never reaches the model or a log. Only an admin can save an agent that
references a secret, and secrets are allowed only in the request path and in a Fixed value
whose Location is header or body. See
Secrets and variables.
Requests go only to public hosts. If the integration's base URL names a host that resolves to a private or internal address, Tandem refuses the request before sending it. The tool call fails with status 400, and the agent gets that error in place of a response.
Shape what the agent gets back
By default the whole upstream response goes to the model. On a full customer record that's a few thousand tokens the agent has to read before it can say one sentence, which the caller hears as a pause.
Under Response Fields, click Add Field once per value the agent actually needs:
- Name — the key the agent receives it under, e.g.
balance. - JSONPath — where to find it in the response, e.g.
$.account.balance. It's compiled when you save, so a malformed expression is an error you see now rather than a failure mid-call. - Default — optional. Used when the expression matches nothing. Leave it empty and the key is left out; the agent then has nothing to read aloud, which is better than "your balance is null."
Leave the list empty and the agent gets the whole payload. With entries, it gets the status plus only the fields you named. A non-2xx response skips extraction and returns the whole body, since an error payload doesn't have the shape your expressions were written against.
Wrap-up tools
A wrap-up tool is a generic integration tool that can still run after the call ends, when the agent didn't get to it. The usual case is filing a support ticket for a caller who reported a problem and then hung up.
Only a generic integration tool can be a wrap-up tool. A Zendesk, Slack, or Meridian action tool can't, so to file a Zendesk ticket this way, point a generic integration at the Zendesk API.
To set one up, switch on Wrap-up tool on the tool's row, below Response Fields. A Wrap-up condition box appears. Describe, in plain language, when the tool should run:
The caller reported a problem that was not resolved during the call.The tool stays in the agent's toolset, and the agent sees it exactly as before. It can still call the tool during the call, for example to read a ticket number back to the caller.
What happens after the call
If the agent called the tool successfully during the call, or its call may have gone through
(see below), nothing more happens. Otherwise, shortly after the call ends, a model reads the
call's transcript and decides whether your condition is true. If it is, the model fills in the
tool's Model parameters from the transcript and Tandem sends the request. Fixed and
Call parameters are filled the same way they are during a call, so a phone number parameter
set to call.ani gets the caller's number.
A call in which the caller said nothing never runs a wrap-up tool after the call. When a caller rings back after a dropped call and the agent resumes that conversation (see Reconnect after a dropped call), the condition is judged only on what was said after the reconnect. The dropped call ran its own wrap-up when it ended.
If the call moved to another agent, only the wrap-up tools of the agent the caller first reached can run after the call. The agent the call moved to runs none of its own wrap-up tools after the call, but if it has a wrap-up tool with the same name as one of the first agent's and calls it successfully, the first agent's tool counts as Sent during the call. The summary one agent passes to the next is shown to the model as a handoff note, so it isn't read as something the caller said.
When the agent hands the call to a workflow with a Workflow (Handoff) tool, no wrap-up tool runs at the handoff. When the caller comes back to the agent, the check after the call covers the whole call. What the agent sent before the handoff still counts as Sent during the call, and a tool is never sent twice across the two parts. If the caller hangs up while in the workflow, or the workflow ends the call, no wrap-up tool runs, and the Wrap-up section says the call was handed to a workflow.
Test This Tool sends the request the way a call would. It doesn't run the wrap-up check.
At most once after the call
After the call, Tandem sends each wrap-up tool's request at most once. Whenever it can't tell whether a request arrived, it doesn't send it again: a missing ticket can be filed by hand, and a duplicate is harder to undo.
- If the agent's attempt during the call failed before it was sent, or the integration refused it
(a
4xxerror other than408), Tandem still checks the condition after the call and can send it then. - If a request timed out, was still waiting for an answer when the call ended, or the integration
answered with a server error (
5xx) or a redirect, Tandem can't tell whether it arrived. The tool is marked Unknown and isn't sent again. - If the request sent after the call is refused, or can't be built from the transcript, it's marked Failed and isn't retried.
A successful request to a SOAP integration reads as Unknown, during the call and after it. So a SOAP wrap-up tool that the agent called successfully during the call is never sent again.
One case can send a tool twice: a workflow with two AI Agent nodes whose agents both have a wrap-up tool with the same name. The check after the first agent's part doesn't see what the second agent does live, so the second agent can call the tool during the call and the first agent's check can still send it. To avoid it, give the wrap-up tool to only one of the two agents.
When a tool shows Unknown, check the destination (your ticketing system, for example) before you create the record by hand.
Transcripts and your callers' data
A wrap-up tool reads the transcript, so an agent with one stores its call transcripts even when Store conversation transcripts is off. The Safety card says so when that happens. See Transcript retention.
The request is built from what the caller said, so it can carry a summary of their problem, their name, their phone number, or account details. It goes to the integration you configured after a call where the condition is true, unless the agent's own call to the tool during the call succeeded or may have gone through. Redact customer PII doesn't change this: a stored transcript holds the caller's words unmasked, and the parameters are filled from it.
See what a wrap-up tool did
Open the call from Insights → Call Logs with the row's View Transcript button. Above the transcript, a Wrap-up section lists each wrap-up tool the agent called during the call or that the check after the call decided on, and what happened to it. A notice at the top of the section says when that check is still running or didn't run at all; a tool the agent never called has no row until the check decides on it.
| Shown | What it means |
|---|---|
| Sent | Sent after the call. Shows what the integration returned, such as a ticket number. |
| Sent during the call | The agent called it successfully, so nothing ran after the call. |
| Sending | The request is being sent now. |
| Not needed | The condition wasn't met. Shows why. |
| Failed during the call | The agent's call failed and nothing ran after it. The notice above says why. |
| Failed | The request didn't go through. Shows the error. |
| Unknown | The request may have gone through. Check the destination before re-creating it. |
| Pending | The agent's call failed; the check after the call is still running. |
| Unavailable | The check after the call hadn't finished an hour after the call ended. Check the destination. |
What Sent shows follows your Response Fields: the fields you named, or the whole response
if you named none. A response over 8 KB, or one that can't be stored, is kept as its status code
only and shows as, for example, 200 (response not stored).
Test a tool before you call
Generic integration tools have a Test This Tool section, so you don't need a phone call to
find out whether the request is right. A Zendesk or Slack tool has none — there is no request of
yours to preview; use Test Connection on the integration itself instead. Fill in a sample
value for each Model parameter — call.ani and call.id too, if you use them; agent.id
and org.id are sent for you — then:
- Preview Request resolves the request and shows it without sending anything.
- Run Live actually calls your API. On a method that changes data it asks for confirmation first, naming the request it's about to send.
The section also works on a published version, so you can run both buttons against the tools callers reach.
A live run shows the Resolved Request, the Raw Response with the status your API returned, Sent to the Model, and the Response Headers. The raw response and what the model receives each show a byte count, so you can see how much your Response Fields cut. Each payload has a copy button and an expand button that opens it in a larger window.
Nested request bodies
A parameter's type can be Object instead of a scalar. That turns it into a container: a
group that holds its own nested parameters instead of a value of its own. Nesting is available
only for a Body parameter on an integration tool — a workflow or handoff tool's
parameters become variables, which have no place to put a nested value, so object isn't
offered there.
A container has no Model, Fixed, or Call choice, because it has nothing to fill — only its children do, and each child chooses independently. A child can itself be another object, down to the depth limit below. A nested child never sets its own location: it always goes wherever its top-level ancestor does.
A Fixed leaf never reaches the model, at any depth. That's true at the top level today, and nesting doesn't change it — an author-set value inside an object is exactly as invisible to the model as one outside it.
Limits:
- A parameter tree may nest at most 3 levels deep, counting the top level as level 1.
- A tool may declare at most 50 parameters in total, containers included — so a container is not free even though it has no value of its own.
Worked example
Say the integration expects this body:
{ "type": "select", "condition": { "objectId": "1001", "objectType": "utilityProvider" } }You'd author it as three parameters, one of them nested:
type— string, Body, Fixed:"select"condition— object, Body, described for the model as what it should fill in. Holds:objectId— string, Model, required, described as the item's IDobjectType— string, Fixed:"utilityProvider"
At call time, the tool builds exactly the body above — type and objectType come from what
you set, and objectId from whatever the model supplied (here, "1001").
But the model never sees type or objectType; both are Fixed, so they're pruned from the
schema it's shown. What it sees is only:
{
"type": "object",
"properties": {
"condition": {
"type": "object",
"description": "The item to select.",
"properties": {
"objectId": {
"type": "string",
"description": "The item's ID."
}
},
"required": ["objectId"]
}
}
}condition survives because it has at least one Model child (objectId); a container whose
every child is Fixed or Call is pruned from the schema entirely, at any depth — the model is
never shown an empty object to fill in.
Add an MCP tool
Choose MCP as the kind and pick the MCP integration. That's the whole row — no name, description, or parameters, because the MCP server publishes those itself and the agent reads them when the call starts.
Which tools the agent can reach is controlled on the integration, by the tools you've enabled there. Because you don't write those descriptions, test an MCP-backed agent on real calls before trusting it: the agent's judgment about when to use each tool is only as good as the descriptions the server ships.
The integration's Server URL must start with https://, and Tandem does not follow
redirects from it. A server that answers with a redirect of any kind, including one that only
adds a trailing slash, fails in two places. Loading its tools on the integration fails with an
error saying the server redirected. And when a call starts or a chat message arrives, the agent
fails to connect to it and answers without that server's tools; the caller or visitor sees no
error. Save the URL the server answers on directly. If an MCP-backed agent never uses its tools,
check the integration's Server URL for a redirect.
Loading a server's tools on the integration also fails when the Server URL's host resolves to a private or internal address.
Add an agent tool
Choose Agent as the kind and pick another agent in your org from the second dropdown, then name and describe the tool the same way you would a workflow tool — the Description is what tells the caller-facing agent when to reach for this specialist.
Unlike the other kinds, an agent tool has no Parameters to declare. The caller-facing agent always hands the linked agent a single message describing what it needs; the linked agent decides what to do with it and hands back a plain-text answer, which the caller-facing agent then speaks to the caller. The linked agent runs its own persona, model, and tools for that one exchange. It never talks to the caller directly, and it starts fresh on every call to it, so the caller-facing agent's message has to carry whatever context the linked agent needs.
A linked agent can't itself link another agent. You can build one level of delegation — a caller-facing agent consulting specialists — but a specialist's own tool list may hold any kind except another agent tool. This keeps a call from chaining through an unbounded sequence of agents. It also can't be linked to itself.
Add a node tool
A node tool runs one workflow node directly, in place, when the model calls it — the same node type you'd drop on a workflow canvas, minus the canvas. The caller stays with the agent throughout, exactly as with a Workflow (Invoke) tool; the difference is scope, not call behavior. Reach for a workflow tool when the action needs several steps or branching logic, and for a node tool when it's genuinely one step.
Add the row and choose Node
Click Add Tool, then set the kind dropdown to Node.
Pick the node
The Node dropdown lists every node type, grouped the same way the workflow canvas groups them. Most are shown disabled with the reason underneath their name — see Why a node is unavailable.
Name and describe it
Same rules as a workflow tool: a snake_case verb phrase the model calls, and a description that says when to use it. See Add a workflow tool above.
Fill in the node's own fields
Below the picker, the node's own field editor appears — the identical form you'd see configuring that node on a workflow canvas. Every field starts author-fixed: you set it once, and it's sent the same way on every call.
A field whose type is a plain scalar — text, a text area, a dropdown, a switch, a number, a phone number, or an email address — carries a Model-filled switch beneath it. Turn it on and the field moves out of the node's own editor and into the Parameters list below, where it works exactly like a workflow tool's Model/Fixed/Call parameter (see Parameters above) — except it fills that node field instead of a workflow's starting variable.
A field whose type isn't on that list — a dynamic dropdown populated from your data, an array of objects, rich text, a date picker — has no switch at all. It stays author-fixed, always. Those are precisely the field types a model must not be trusted to fill freely, so the option to hand one to the model is never offered.
Save
Click Save Configuration. The tool is available on the agent's next call. On an agent that is on versioning, you'll be editing a draft — the button reads Save as New Draft if you started from the published version — and the tool reaches callers when you publish that draft.
Reading the result
A node tool answers with {outcome, data}. outcome is the exit name the node itself took —
the same name you'd see as an edge label on the workflow canvas. It is carried over verbatim, not
translated into a generic success/failure flag. data is the node's response payload,
trimmed by Response Fields exactly as an integration tool's
is, if you've added any.
This matters for how you write the description and the system prompt: a node's built-in exits
like fallback, invalidInput, or timeout come back as an ordinary outcome value, not as a
tool error. The model is meant to read them and recover in the conversation ("the email address
didn't work, want to double check it?"), the same way it would react to any other spoken answer.
One outcome has no matching edge on the canvas: a node that stops the run outright rather than
leaving by a named exit answers with outcome: "terminal". Treat it as a failure the agent
should acknowledge rather than retry.
A tool call only reports {error} for what the model genuinely cannot act on: the node
throwing, the call exceeding the tool's timeout, or a node type this environment can't run at
all.
Why a node is unavailable
Whether a node can be bound is not a hand-picked list. It is derived from what this environment can actually do, checked in this order:
- The node is deprecated. It's being phased out and was never opened to new tool bindings.
- The node can pause the call. A node that waits for something outside itself — a reply, a scheduled follow-up — can't complete as a single synchronous tool call. A small number of these become bindable once you fix the field that would trigger the wait to its non-waiting value; the picker only lists them as available once you've done that.
- The node has no implementation in this environment. Most of the workflow node catalog isn't reachable from an agent at all yet.
- The node needs something this environment doesn't have. A node that reads or writes data this call-handling environment has no access to is correctly unavailable rather than silently failing partway through.
An unavailable node is always shown, disabled, with the specific reason underneath its name — never hidden. If a node you need isn't offered, the reason tells you which of the above applies; it isn't a bug to work around.
Available today: Log Event, Send Email, and Send SMS. More node types open up as this service grows what it can run — check the picker for the current list rather than treating this as fixed.
Comma-separated lists
A few node fields — a Send Email recipient list, for example — are a list of values on the
workflow canvas. A model can't be handed a real list as a parameter type, so when you make one
of these fields model-filled, the model instead fills in a single comma-separated string, which
is split back into the list the node expects before it runs. This is safe for addresses
specifically because a valid email address can never contain a comma, so splitting on , never
cuts one in half. Treat it as a convention for this field type today, not a general way to hand a
model a list — true list parameters may arrive later.
Writing tools an agent uses well
Tandem enforces none of this.
- One job per tool. A
manage_accounttool that reads, updates, and cancels will be called for the wrong one. Split it into three tools with three descriptions. - Say when to use it, not just what it is. "Use this when the caller asks about delivery timing" gives the model a trigger. "Order lookup endpoint" doesn't.
- Describe every Model parameter. An undescribed parameter gets filled with a plausible guess — and a save without one is rejected.
- Don't ask the model for what the call already knows. The caller's number is
call.anias a Call parameter. Asking the model for it invites a wrong answer and lets a caller name someone else's. - Mark the required ones required. Otherwise the agent will happily call the tool without them.
- Say what the tool returns, in the description, if the shape isn't obvious. The agent has to turn that result into a spoken sentence.
- Trim the response. Use Response Fields to send the five values that matter instead of the whole record. It produces better answers and faster ones.
- Name the tool for the caller's intent, not your system's internals.
check_balancebeatsacct_svc_v2_query. - Set a thinking cue once an agent has any tools. Every tool call is a pause, and an uncovered pause sounds like a dropped call.
- Say what to do on failure in the system prompt: "If the lookup fails, apologize and offer to take a message." Otherwise the agent improvises.
Limits and defaults
| Setting | Value |
|---|---|
| Tools per agent | No limit |
| Tool name | Letters, numbers, hyphens, underscores; up to 64 chars |
| Tool name | Must be unique within the agent |
| Tool name | Can't be one of the platform's own (see below) |
| Description | Required on every kind except MCP |
| Parameters | Up to 50 per tool, containers included; name up to 64 chars |
| Parameter nesting | At most 3 levels deep; Object only on an integration body |
| Parameter Type | string, number, integer, boolean, object |
| Parameter Location | path, query, body, header — required on integration tools |
| Response Fields | Up to 50 per tool; name up to 64 chars |
| Integration Method | GET, POST, PUT, PATCH, DELETE — generic integrations only |
| Integration Request path | Required; must start with / — generic integrations only |
| Wrap-up tools | Up to 5 per agent; generic integration tools only |
| Wrap-up condition | Required when Wrap-up tool is on; up to 2,000 characters |
| Zendesk tool | No parameters and no Response Fields; Action is Create Ticket or Show Ticket |
| Slack tool | No parameters and no Response Fields; a channel or person is required |
| MCP tool | No name, description, or parameters — discovered at runtime |
| Agent tool | No parameters; cannot target itself or a chain of agents |
| Node tool | nodeType/version pinned at save time; only bindable nodes offered |
These names are taken by the platform's own tools and can't be reused: end_call,
search_knowledge_base, skills, transfer_to_external, transfer_to_voice_agent,
capture_dtmf, pause_recording, resume_recording, get_current_time, and
set_workflow_variable. See Built-in Tools.
Changing a tool's kind keeps the name, description, parameters, and response fields, and clears only the target you'd picked, so correcting a mis-set kind doesn't cost you the rest of the row.
Troubleshooting
The agent never uses a tool. Nearly always the description. Rewrite it to name the situation ("Use this when the caller asks…") rather than the endpoint. Also confirm the tool saved — check the row is still there after a reload.
The agent uses the wrong tool. Two descriptions overlap. Sharpen the boundary in both, and consider whether they should be one tool.
The agent calls a tool with empty or invented arguments. Every Model parameter needs a description, and the ones the tool can't work without need Required on. Open What the model sees to confirm the schema is what you think it is.
A value never reaches the API. Only Model parameters appear in What the model sees; a Fixed or Call parameter is added on the way out. Use Preview Request to see the fully resolved request.
"Tool … path references {customerId} but no path parameter declares it." Path
placeholders aren't inferred. Add a parameter named customerId with Location path — the
editor offers an Add customerId button that does exactly that.
"… header … cannot be filled by the model; use a fixed or context value." Headers are author-supplied by design. Switch that parameter's Value from to Fixed or Call.
"… cannot be in the body on a GET; use a query parameter." Change the Location to
query, or change the Method.
"… must be filled by the model; fixed and call values are not passed to the workflow." A Workflow (Handoff) tool takes Model parameters only. Switch to Workflow (Invoke) if the tool needs fixed or call values, or drop those parameters.
"Duplicate tool name" or "reserved tool name." Names must be unique within the agent, and the ten platform names listed above are taken.
"Tool … needs a wrap-up condition." Fill in Wrap-up condition, or switch Wrap-up tool off.
"Tool … wrap-up condition is limited to 2000 characters." Shorten the condition. The limit applies with the switch off too, because a switched-off tool keeps its condition.
"An agent can have at most 5 wrap-up tools." Switch Wrap-up tool off on the tools that don't need to run after the call.
A wrap-up tool didn't run after a call. Open the call in Call Logs and read its Wrap-up section. Sent during the call and Not needed are the usual reasons. Unknown means it may already have gone through, so it isn't sent again, and Failed during the call means the agent's own attempt failed and the check after the call didn't run; the notice above the rows says why. A call in which the caller said nothing never runs one after the call.
Saving fails on a secret reference. Only an admin can save an agent that references
{{secrets.*}}, and a reference is only accepted in an integration tool's request path or
in a Fixed value whose Location is header or body.
The agent goes quiet during a tool call. Set a Thinking cue on Voice & Speech.
The agent won't take an action even though the tool exists. Check Safety — a steering rule may be blocking that call.
"An agent tool cannot target the agent it is configured on" / "already links another agent as a tool." Self-reference and chaining through a second agent tool aren't allowed — see the depth limit above. Point the tool at a specialist that has none of its own agent tools.
A node is grayed out in the picker. That's not a bug — it's shown disabled with the reason underneath its name. See Why a node is unavailable.
A node tool's result reads like an error but the call kept going. That's expected for a
system exit (fallback, invalidInput, timeout) — it arrives as an ordinary outcome value,
not a tool failure, so the model can recover conversationally. See
Reading the result.