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.

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. Values are scalars or strings with {{variable}} placeholders. Omit to inject 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.
string
A fixed spoken sentence with no {{variables}}. Omit for no announcement. Legal on local, webhook, knowledge, and hosted slng tools.
tools/search_places_text.yaml
This snippet is separate from the phone-ready hotel-concierge example. Declare customer_name under variables: with type: str and source: call_start, 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. Injected values keep their exact type, including false and 0, and are never shown to the model. {{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: "{{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 same two lists:
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 inject 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

SLNG needs nothing from your package beyond the name. LiveKit and Pipecat are different: they build and run the tool themselves, so they need a real copy of its definition, and for a code tool its module, sitting in your package. That copy is what unmute pull fetches:
The reference line is the portable part. slng: check_order is the same on all three targets, and nothing about the tool file changes between them. What does not travel is one package selecting slng and a code target at once. A code target needs a turn: binding to place its end-of-turn detector, the slng target refuses one because SLNG runs its own turn taking, and a per-target models: override cannot add a binding the agent does not define. So the code-target build is a second package with the same tool files in it:
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.

Reference-only

Unmute creates no tool on SLNG. 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. That is also why local: and webhook: are refused on an slng target: SLNG owns a tool’s code, version and gate pipeline, so there is nowhere in a package for either block to write to. Both still work exactly as before on livekit and pipecat. A brand new tool starts in the SLNG dashboard; slng: is how your package reaches it once it exists there.

Which block for which job

The last two are the two that no longer reach slng.

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 an injected 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 inject: 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 inject: 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 reference-only block: a capability SLNG curates.