> ## Documentation Index
> Fetch the complete documentation index at: https://unmute.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Unmute compiles to exactly three targets. Pipecat and LiveKit are code targets: compile writes a Python project you run. SLNG is a hosted target: compile writes a deployment body and SLNG runs the agent, so it has no `unmute dev`. Those three are the only values `provider` accepts in `targets.yaml`. Deepgram and ElevenLabs appear in these docs as model vendors, which is not the same thing as a target, and `slng` is both.
> The Go structs in `internal/spec` and `internal/ir` are the schema truth. Check a field against them, or run `unmute validate`, rather than against what you remember.

# Hosted tools

> Reference a tool your SLNG organisation already has by its exact name: no hash, no mirror, no pull required for SLNG.

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:

* [The file name and the hosted name](#the-file-name-and-the-hosted-name-are-two-different-things) - two names, one reference
* [Descriptions and parameters are inherited](#descriptions-and-parameters-are-inherited) - what the platform supplies
* [What the block keeps](#what-the-block-keeps) - every key you write
* [Attaching one](#attaching-one) - the same two lists
* [Deploying to SLNG needs no mirror](#deploying-to-slng-needs-no-mirror) - what deploy checks
* [What each target does with it](#what-each-target-does-with-it) - slng, livekit, pipecat
* [Advanced](#advanced) - the legacy form and the mirrors
* [Troubleshooting](#troubleshooting) - the refusals, and their fixes

```yaml tools/check_order.yaml theme={null}
slng: check_order

announce: One moment while I look that up.
```

`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

<ParamField path="slng" type="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.
</ParamField>

## 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:

```yaml tools/order_status.yaml theme={null}
slng: check_order
```

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:

```yaml tools/check_order.yaml theme={null}
slng: check_order
description: Look up an order and tell the caller its status and delivery date.
```

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:

| Field                         | Because                                                                      |
| ----------------------------- | ---------------------------------------------------------------------------- |
| `input`, `output`             | the platform published the schema; a second copy here could disagree with it |
| `handler`                     | the code is the platform's, not authored here                                |
| `url_env`, `base_url`, `path` | the platform stores the URL                                                  |
| `dependencies`                | the platform installs them                                                   |

## 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:

<ParamField path="description" type="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.
</ParamField>

<ParamField path="inject" type="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.
</ParamField>

<ParamField path="interruption" type="string">
  Accepts `provider_default`, `continue`, or `cancel`. Omitted means `provider_default`.
  Refused on MCP sources. Target support is listed above.
</ParamField>

<ParamField path="effect" type="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.
</ParamField>

<ParamField path="announce" type="string">
  A fixed spoken sentence with no `{{variables}}`. Omit for no announcement. Legal on
  local, webhook, knowledge, and hosted `slng` tools.
</ParamField>

```yaml tools/search_places_text.yaml theme={null}
slng: search_places_text

inject:
  - query: "{{customer_name}}"
```

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](/build/tools/overview#the-three-behavior-fields).

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:

```yaml agent.yaml theme={null}
agents:
  support:
    instructions: instructions.md
    think: reasoning
    speak: front_desk
    tools:
      - check_order
      - end_call

tools:
  - check_order
  - end_call
```

## Deploying to SLNG needs no mirror

```sh theme={null}
unmute validate examples/hotel-concierge --target slng
unmute compile examples/hotel-concierge --target slng
unmute deploy examples/hotel-concierge
```

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](/deploy/slng) for what that run checks and what
it prints.

## What each target does with it

| Target  | What happens                                                                                                                |
| ------- | --------------------------------------------------------------------------------------------------------------------------- |
| SLNG    | resolves the platform's own tool by name at deploy time. Nothing is created, nothing is uploaded, nothing local is required |
| LiveKit | the mirrored code runs inside the generated project                                                                         |
| Pipecat | the same                                                                                                                    |

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:

```sh theme={null}
unmute pull my-agent          # writes the mirror beside each hosted tool file
unmute compile my-agent --target livekit
```

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:

```text theme={null}
my-agent/            targets.yaml selects slng, no turn binding
  tools/check_order.yaml         slng: check_order
my-agent-livekit/    targets.yaml selects livekit, agent.yaml adds turn
  tools/check_order.yaml         the same one line
  tools/check_order.slng.json    written by unmute pull
```

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:`](/build/tools/python) and
[`webhook:`](/build/tools/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

| You want                                                        | Write      |
| --------------------------------------------------------------- | ---------- |
| a tool SLNG already hosts, with code or a request configuration | `slng:`    |
| a capability SLNG curates, like ending the call                 | `builtin:` |
| tools from an MCP server your organisation registered           | `mcp:`     |
| your own Python, running inside a livekit or pipecat project    | `local:`   |
| an HTTP endpoint you host                                       | `webhook:` |

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

```yaml tools/check_order.yaml theme={null}
slng:
  hash: 336a66b9a564f472...
```

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:

```text wrap theme={null}
tool "check_order": tools/check_order.slng.py does not match the hash tools/check_order.slng.meta.json
  pins, so the committed mirror is not the one this package means: run `unmute pull` and
  read the diff, or `git checkout` the mirror if the edit was a mistake
```

`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:

```text wrap theme={null}
tools/check_order.yaml:1: `slng:` names the tool SLNG already hosts: write `slng: check_order`,
  one line, the hosted tool's exact name
```

**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:

```text wrap theme={null}
livekit: tool "check_order": `slng:` names a tool SLNG hosts and this target builds a tool
  out of its committed mirror, and none is committed: run `unmute pull` to fetch it and
  commit what it writes, or compile this package to slng, which references the published
  tool and needs no mirror
```

**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:

```text wrap theme={null}
the LiveKit driver builds one dependency list for the whole generated project from the
  provider catalogue and reads no per-tool pins: add the package to
  build/<target>/pyproject.toml after compiling, or compile to slng which installs a
  per-tool environment
```

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

<Columns cols={2}>
  <Card title="Deploy to SLNG" icon="cloud-upload" href="/deploy/slng">
    What a real deploy resolves, checks and reports.
  </Card>

  <Card title="unmute pull" icon="download" href="/reference/cli/pull">
    Optional, and only for livekit or pipecat: the command, its flags, and every refusal.
  </Card>

  <Card title="MCP servers" icon="plug" href="/build/tools/mcp">
    The other hosted-by-name block: a server SLNG already has.
  </Card>

  <Card title="Prebuilt tools" icon="package" href="/build/tools/prebuilt">
    The other reference-only block: a capability SLNG curates.
  </Card>
</Columns>
