Skip to main content
A hosted tool is not defined in your package. It is defined on the SLNG platform already, and the block just names it. On this page:
tools/check_order.yaml
slng: check_order is the whole reference. The scalar is the hosted tool’s exact name, as your organisation holds it. No description, no schema, no hash, no mirror, and no unmute pull are required to validate, compile, or deploy this to SLNG. A sentence spoken before the tool runs goes on the tool’s entry in agent.yaml, under start::
agent.yaml

Hosted reference fields

string or legacy object
required
The exact published tool name. No hosted name is inferred from a scalar reference. The legacy object accepts hash and resolves the hosted name from the tool file name; see the legacy form below. A tool file must have exactly one execution block.

The file name and the hosted name are two different things

The tool file’s name is still the package reference: it is what an agent’s tools: list attaches, and what every local diagnostic calls the tool. The slng: scalar is the name deployment resolves against your organisation. They usually agree, and they do not have to:
tools/order_status.yaml
The agent attaches order_status; deployment resolves check_order. This creates no tool and renames nothing in SLNG, it only lets your package call the reference whatever reads best in the prompt and the tool list. Say the hosted callable name out loud in a prompt that explicitly names the platform function; the file name is a package reference, not a remote rename.

Descriptions and parameters are inherited

A hosted tool owns its own description and schema. Omit description: and the attachment uses whatever the published tool says; write one and it overrides the platform’s for this attachment only. Delete the field later and inheritance comes back:
tools/check_order.yaml
Nothing here is checked offline. A compile records the description you write as an override, and that is all it does with it. Whether the published tool still means the same thing is a question only your organisation’s copy can answer, so it is one of the checks unmute deploy completes rather than one unmute compile does.

input and output still have nowhere to go

A hosted tool owns its own schema, so these fields are unwritable rather than silently ignored:

What the block keeps

These describe how your agent uses the tool, not what the tool is, so they stay yours to write, exactly as before:
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.
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}}, legal on local, webhook, knowledge, and hosted slng tools. See the tools overview.
tools/search_places_text.yaml
This snippet is separate from the phone-ready hotel-concierge example. Declare customer_name as a str field on State in state.py, give it source: call_start under variables:, and supply it when starting a web session. For inbound phone calls, give it a valid default or leave query to the model: carriers supply no session inputs. Values passed through with: keep their exact type, including false and 0, and are never shown to the model. {{state.customer_name}} is resolved when a call starts; a fixed value such as - limit: 0 is bound as written. Full rules for the last three fields are on the tools overview. A variable does not have to land on a text parameter. SLNG stores the value as text and converts it into the parameter’s declared type, so - limit: "{{state.how_many}}" works on an integer parameter as long as the stored value reads as a number. That conversion happens when the call starts, so unmute deploy checks the parameter and reports the value as deferred rather than claiming to have validated it.

Attaching one

No different from any other tool. The agent names it:
agent.yaml

Deploying to SLNG needs no mirror

All three work with no mirror file, no network, and no credential except the last, which is the one step that has to reach your organisation at all. That is the whole point of naming a tool rather than copying it: the published version is the only copy, so there is nothing local to keep in step with it. What an offline compile genuinely cannot check, because the answer lives in your organisation and not in your package:
  • that a tool called check_order exists there, and which published version is the latest;
  • that every with argument is a parameter of that published version, and that a fixed value is the type it declares;
  • which Vault entries the published version needs.
build/slng/compile-report.json names these under deferred_checks, against the references they apply to. A clean compile therefore reads as “nothing wrong yet” rather than “everything is fine.” unmute deploy completes all three before it changes the agent, and --dry-run reports them without changing anything. See Deploy to SLNG for what that run checks and what it prints.

What each target does with it

A slng: tool is SLNG-only for now. SLNG needs nothing from your package beyond the name. Livekit and pipecat do not compile hosted tools yet. When it does, they will need a real copy of the tool’s definition, which unmute pull fetches. For a scalar reference, the pin that proves the mirror is still the one your package means goes into a generated tools/<name>.slng.meta.json beside the mirror, never into your tool file. Commit everything pull writes. A package that targets slng alone never runs this command at all. Two things pull may still write into files you wrote, so you can read the diff knowing what to expect:
  • A tool file written the older slng: block form has its hash: restamped in place, because that form keeps its pin in the tool file. A scalar reference never gets touched.
  • secrets: in agent.yaml gains any credential name a mirrored tool needs and your package does not declare yet, and only when a target builds the tool itself. LiveKit and Pipecat read that list to get the credential into the generated project. A package targeting slng alone gets nothing added: the platform reads the credential from its own vault, and unmute deploy reports the same requirement from the published contract.

A reference points at an existing tool

A hosted reference only ever points at a tool your organisation already has. A name it does not hold is a refusal, not something a deploy creates for you. A local: tool is different. On an slng target, unmute deploy publishes it as a code tool, so you do not write slng: for it. A webhook: block is still refused on an slng target. Create an API request tool in SLNG and reference it with slng:. Both blocks work as before on livekit and pipecat. You can also start a code tool by hand with voiceai tool create. slng: is how your package reaches it once it exists there.
The file defines a pydantic Input and Output and a handler(input: Input) -> Output. SLNG refuses a few schema keywords in Input, so a Field(pattern=...), a Decimal or a fixed-length tuple fails the build. A date, a time, a UUID or an email address is fine. A push attaches only a published version.

Which block for which job

The last two are the two that no longer reach slng. A curated capability resolves either way: slng: current_datetime attaches it by name, and so does builtin: {id: current_datetime}. Reach for builtin: whenever the capability takes a setting of its own, because with: under a slng: block is an argument and a curated capability publishes no argument schema to put one in. See prebuilt tools.

Advanced

Neither of these comes up on a package that targets slng and writes the scalar reference.

The legacy form still loads

tools/check_order.yaml
A tool file written before this shape existed still resolves, by the file’s own name, and a code target still checks its mirror’s pin exactly as it always did. It is accepted, not taught: a new tool file should write the scalar name instead. Nothing about deploying to SLNG reads this form’s mirror or its pin; SLNG resolves the reference by name either way.

The mirrors are not yours to edit

tools/<name>.slng.json and, for a code tool, tools/<name>.slng.py are the platform’s copy, not yours, whichever form the reference is written in. The .slng. infix marks a file as mirrored rather than authored, so one glance answers whether you may edit it. The answer is no. Every compile of a code target checks the mirror against its pin, with no network at all, so a hand edit is caught the next time anybody builds the package:
unmute pull itself refuses the same way, before it overwrites anything, naming every changed file at once. --force discards the edits; the honest fix is to change the tool on the platform and pull again.

Troubleshooting

The build refuses a bare slng: block

Leave the block bare and validation refuses before anything else runs:
Fix: write the hosted tool’s exact name on the same line, as your organisation holds it.

unmute deploy refuses a with: value

There are three cases, each naming the tool file and the published version:
  • The parameter is not declared. An attachment pins a declared parameter. A schema that allows extra properties lets the model send more; it does not give an override anywhere to bind.
  • The parameter’s type is not settled. A parameter that could be a string or an integer, or that declares no type, or that holds an object or a list, cannot take a pinned value. Leave it to the model.
  • A constraint sits at the root of the schema. A published schema may constrain a parameter from its root, under allOf and friends. Checking one supplied value against a rule written for the whole call would reject the arguments the model fills in, so the value is not reported as checked.
Fix: drop that key from with: and let the model fill the parameter in, or pin a different parameter whose published type is settled.

Two tool files resolve to one hosted tool

Two tool files resolving to the same hosted tool are refused, naming both. SLNG attaches a hosted tool once, so the two cannot carry separate descriptions or with: values, and a push would silently keep one file’s settings and drop the other’s. Fix: keep one file per hosted tool, and attach that one file wherever both were attached.

A code target says no mirror is committed

Skip the pull for a code target and the refusal names the fix, and the shortcut:
Fix: run unmute pull and commit everything it writes, or compile the package to slng instead.

A hosted tool with Python dependencies is refused on livekit and pipecat

One limit, and it is the honest cost of “runs everywhere.” A hosted tool that declares Python dependencies compiles to slng, which installs a per-tool environment. It is refused on livekit and pipecat, in the same words an authored local: tool’s dependencies: already gets there:
A mirrored pin and an authored one reach nothing on those two targets for the same reason: each builds one dependency list for the whole project, and reads no per-tool pins. A hosted tool with no dependencies works on all three. On a slng-only package this limit is invisible until deploy, because there is no mirror to read it from ahead of time; deploy reports it as one of the checks it completed. Fix: add the package to build/<target>/pyproject.toml after compiling, or compile that tool’s package to slng.

Where to go next

Deploy to SLNG

What a real deploy resolves, checks and reports.

unmute pull

Optional, and only for livekit or pipecat: the command, its flags, and every refusal.

MCP servers

The other hosted-by-name block: a server SLNG already has.

Prebuilt tools

The other block that names a tool SLNG already has: a capability SLNG curates.