tools/<name>.yaml.
The file stem is the tool name. It must be lower snake case and cannot start with
an underscore.
On this page:
- The eight execution blocks - the ways a tool runs
- How each target treats a tool - what each one supports
- Choosing a kind - stop at the first fit
- Fields inside each execution block - every key, per block
- Which fields the block allows - the shared contract fields
- The three behavior fields - start: say, effect, interruption
- Define once, attach by name - one definition, one list
- Troubleshooting - the refusals you will meet
tools/find_slots.yaml
tools/salon.py is the input, and its Pydantic return model is the
output, so the file has no input: or output:. A webhook tool writes both as
JSON Schema instead. Write the description as an instruction rather than a
label, and let the types do real work: a Literal["haircut", "hair-color", "blowout"] on the service argument means the model cannot ask for a service
the salon does not offer.
The signature is also the whole argument list. Generated Pipecat tools turn an
extra argument into a normal corrective tool result, so the model can retry
with only the declared fields. A handler failure before any result is returned
the same way, without exposing the private exception to the model or leaving a
call stuck in progress. Keep workflow prerequisites in the prompt; do not make
their names look like extra tool inputs in the description.
The one block near the bottom is the execution: it says how the tool runs. Every
tool file has exactly one.
The eight execution blocks
Exactly one, and the compiler holds you to it. A file carrying two blocks, and a
file carrying none, are both refused with their line: see
Troubleshooting.
The two gated blocks
client: and provider_hosted: exist in the schema and no target emits them. The
capability table denies both on every provider, so writing one fails with the
target named:
client: {} or provider_hosted: {}.
How each target treats a tool
Twilio runs typed handlers, and the only builtin it takes is
end_call. The target runs one agent with no tasks, and it refuses
start: say and interruption: cancel. The Twilio target
lists every refusal.
On livekit and pipecat, the table is narrower: they compile only local:, webhook:, knowledge: and the end_call builtin: they refuse mcp:, slng: and every other builtin, so those are SLNG-only for now.
The same tool file compiles to three different runtimes, and they do not all
support the same things. This table is the capability table, which is what the
compiler actually reads, so a no here is a refusal you will meet at compile time
rather than a surprise on a call.
Three of those rows are worth a sentence.
local: reaches SLNG as a code tool, and webhook: does not. On an slng
target, unmute deploy publishes a typed local: tool to SLNG. It needs a
tools/<name>.sample.json beside it (details).
A webhook: block is refused. An API request tool is created in SLNG and
reached with slng:. Both blocks work as before on LiveKit and Pipecat.
mcp: and slng: are SLNG-only for now. LiveKit and Pipecat do
not compile them, and the compiler refuses them. Builtins other than end_call are
refused there too. SLNG resolves a hosted reference by name at deploy time: no mirror,
no hash, no unmute pull. See Hosted tools and
MCP servers.
knowledge: needs a runtime of ours to live in. LiveKit and Pipecat compile to
a Python project, so the documents ride in the image and the search runs in the
process. The SLNG target writes a deployment body and SLNG runs the agent, so
there is no image to carry a folder and no process of ours to index it in. Put the
facts in the agent’s instructions, or compile to a code target.
client: and provider_hosted: are names with nothing behind them. They exist
in the schema and no target emits them. They are documented so a refusal reads as
a decision rather than a bug.
Attaching a tool to a task
A tool listed on a task rather than on an agent is a narrower thing, and two kinds
are not available everywhere.
Pipecat’s reason is the same for both: a task tool there is a flows handler holding
a
FlowManager, not a decorated function holding FunctionCallParams. SLNG takes
no knowledge: tool anywhere. On SLNG a tool that only a task lists is attached
hidden from the main agent.
The behavior fields
LiveKit runs a tool to completion, so a per-tool interruption value has nothing to
act on and it says so. SLNG owns its own turn taking and has no per-tool setting at
all.
Every refusal above names the target and tells you what to do instead. If you get
one you do not understand,
unmute validate prints the same message with the tool
name attached.Choosing a kind
Work down this list and stop at the first one that fits.
Prefer fewer tools. Every tool is text in the model’s context on every turn,
and two tools with overlapping descriptions is the most common reason a model calls
the wrong one. A tool the agent list does not name is not offered at all, which is
the cheapest way to narrow a choice.
Write the description as an instruction, not a label.
description and
the input types are the whole of what the model knows. Say when to call it, and say when
not to.
Fields inside each execution block
Each tool file declares exactly one execution block. Its page lists the fields, types, required conditions, and defaults:- Webhook:
url_env,base_url,path, andauth. - Python:
handleranddependencies. - MCP:
server,url_env,transport,auth, andtools. - Prebuilt:
idandinstructions. - Knowledge:
base. - Hosted:
slngnames a published tool.
client and provider_hosted take an empty object and are gated on every target.
SLNG accepts published tool references and typed local: handlers, which deploy publishes as code tools. It refuses authored webhook bodies.
Which fields the block allows
The contract fields are shared, with one exception that matters:string
Required for local, webhook, and knowledge tools: explains when the model should call
the tool. Builtins use their registry description if omitted; hosted
slng tools
inherit their published description. Refused on MCP sources.object
Required for authored webhook contracts: a JSON Schema with
type: object
describing model arguments. A local tool leaves it out, because its handler
signature is the input. Refused on builtin, MCP, knowledge, and hosted slng tools,
which own their schemas.object
An author-side JSON Schema with
type: object. Omit for no declared result schema. Used
by save: rows and outcome matches; it is not a general runtime result validator or a
model prompt. A local tool leaves it out, because its return model is the
output. Refused on builtin, MCP, knowledge, and hosted slng tools.list of one-key pairs
Hidden argument/value pairs. A value is
state.variable, a scalar, or a string with
{{state.variable}} placeholders. Omit to pass nothing. Legal on local, webhook, and
hosted slng tools; builtin send_sms requires its literal from_number setting.string
Accepts
provider_default, continue, or cancel. Omitted means provider_default.
Refused on MCP sources. Target support is listed above.string
Accepts
returns_data or ends_conversation. Omitted means returns_data, except
builtins whose effect comes from the registry. Refused on MCP and knowledge tools.list of strings
Written on the tool’s entry in
agent.yaml, not in this file. A fixed spoken sentence
with no {{variables}}. Omit for no line. Legal on local, webhook, knowledge, and
hosted slng tools.mcp: file carries none of them, because the server owns each tool’s contract.
A builtin: file takes no input or output. The registry supplies its contract
and default description, and your description is added on top if you write one.
A knowledge: file takes no input, output, with or effect either: the
tool asks for one string and returns passages, so there is nothing to describe and
nothing to merge into. It does take description and a start: say: line on its entry in agent.yaml, and
you should write both.
The three behavior fields
tools/check_calendar.yaml
agent.yaml
string
Accepts
provider_default, continue, or cancel. Omitted means provider_default.
Refused on MCP sources. Target support is listed above.string
Accepts
returns_data or ends_conversation. Omitted means returns_data, except
builtins whose effect comes from the registry. Refused on MCP and knowledge tools.start: say: line is the one of the three written on the tool’s entry in
agent.yaml, as in the file listing above. All three are optional, and each is honored differently per target. Pipecat maps
interruption onto its own cancel-on-interruption setting, while LiveKit runs
tools to completion, so a non-default value warns there. effect is fixed by the
registry on a builtin: tool, and a conflicting value fails.
Using start: say:
The line is spoken once, as the tool starts, before the tool’s own work. Nothing
waits for it to finish playing, so the caller hears the tool’s answer no later
than they would without the line. It covers the wait, it does not add one.
Reach for it when the tool is slow enough that the silence reads as a dropped
call. A request to a service you do not control, a handler that queries a
database or a calendar. If the tool answers instantly, the announcement is just a
sentence in the way.
Do not put one on every tool. Two tools firing back to back means the caller
hears two announcements in a row, which is worse than the pause you were trying
to cover.
Writing the line
Writing the line
The sentence is fixed, so it is spoken word for word every time that tool
runs. Everything else follows from that.
Pick something that still sounds fine the third time. If the model calls a
tool repeatedly within one conversation, that is an argument for not
announcing it at all.
The mistake worth avoiding
The mistake worth avoiding
If your instructions already tell the agent to say it is checking something,
delete that line when you add
start: say:. Otherwise the model speaks its own
version, the tool speaks the fixed one, and the caller hears both.The rules
The rules
It adds no new interruption rule: if the caller speaks over the line, the
tool’s own
interruption: value decides what happens. LiveKit emits the line
for a tool listed on an agent or on a task. A target whose driver has no
lowering for the field fails validation with that driver’s own reason, rather
than dropping the line quietly.Define once, attach by name
Define each tool once. The full definition exists only intools/<name>.yaml: description, input and optional output on a webhook tool,
optional with, and one execution block. A local handler lives beside it in a
Python file, and the block names its function as tools/<file>.py:<function>.
Do not put any of those fields in agent.yaml.
Every file in tools/ is a tool, so there is no list that loads them. Every tools: entry in an agent’s or task’s list in agent.yaml is a string name:
agents[].toolsgrants an agent access, andagents[].tasks[].toolsgrants one nested task access.
tools: list in agent.yaml is only for a tool that needs behaviour on every agent. Each entry is a block with save:, outcomes:, hidden: or start:. A bare name there is refused.
agent.yaml
agent.yaml
description, input, output, local, or
webhook.
A tool’s optional output: remains its own JSON Schema. A task does not copy
that schema. Its save: list derives finish fields from destination
variables and saves only the values needed later.
Troubleshooting
A tool file has two execution blocks, or none
Every tool file runs exactly one way, so both shapes are refused. Two blocks:A tool you wrote is never offered
A file intools/ that no agent or task lists is loaded but nobody may call it.
Fix: check the agent’s or task’s own tools: list.
output: is refused on an slng target
A local: tool’s Output comes from the return model of its Python function.
A tool SLNG hosts carries its own Output model. So the field has nothing to
set and is refused rather than dropped.
Fix: delete the field from the tool file. A hosted tool carries its own
Output model, written where the tool was written. Full story in Python
tools and Hosted
tools.
Where to go next
Webhook tools
The everyday case: call your own API.
Python tools
When the call needs code of your own.
MCP servers
Offer a whole server’s tools at once.
Prebuilt tools
The ones the runtime already has.
Knowledge bases
Answer from a folder of your own documents.