Skip to main content

What it needs

Two things. The voiceai CLI, on your PATH. SLNG hosts the agent, and voiceai is the tool that owns your account and the push. Unmute opens no connection to the SLNG agents API itself, at compile time or any other time.
A key. SLNG_API_KEY is read first, then VOICEAI_API_KEY, then whatever profile voiceai login stored. One SLNG key serves every SLNG role, so this is the same key a generated livekit or pipecat project reads at run time.
Get one at app.slng.ai/api-keys.

Usage

A slng: reference needs no unmute pull first, no mirror, and no hash: unmute deploy resolves it directly against your organisation, checks it, and attaches the version it checked. This needs a voiceai release that supports that checked, resolved push; an older one is refused with upgrade guidance before anything is written. See Hosted tools. A clean run reads:
The first line is validate’s own row, and the sentence after it is there because a clean local result is narrower than a clean deploy: it says which checks this command has not made. Each attached line names a reference this run resolved and checked against your organisation, never one it created. Every tool it names already existed, published, before this run started. v<n> is the version this run checked and attached, which is always the latest published one. An MCP selection carries no version, because a server tool has none; what was checked for it is the schema hash SLNG’s own snapshot recorded. The requirements satisfied count is the same account read, folded in with every Vault entry, MCP server and builtin the package needs. Only a slng target is deployed. A livekit or pipecat target compiles to a project that somebody else’s platform runs, so it is unmute compile plus that platform’s own deploy step. Deploying a package with no slng target names the block that would add one. The organisation is printed on every run because an exported key and a stored profile can belong to different ones, and nothing else on screen would tell you which you just wrote to.

Four stages

Validate. The same checks as unmute validate, against the slng target only. The slng target refuses what SLNG will not run, and hearing it here costs no network call. Preflight. Your organisation is asked what it already has, and the answer is compared with what the package needs. A real run can refresh an unusable MCP snapshot or fill a missing Vault entry with consent before later checks finish. A dry run does neither. Compile. The same output as unmute compile --target slng, written to build/slng/. Compiling as part of deploying is deliberate: it means you cannot push an artifact that is older than the package. Push. Checked references are staged with their resolved IDs and versions, then passed to voiceai agents push with --require-resolved and --expect-org. A direct push of build/slng skips Unmute’s binding checks and resolved staging; use unmute deploy for this workflow.

What the preflight checks

Four kinds of read, plus one per MCP server your package names and one per slng: reference, to check the published version it would attach. The cost does not grow with how many secrets you declare. Unmute creates no tools or MCP servers. SLNG owns tool creation entirely, and an authored local: or webhook: block is refused before this step is reached. So there is no first-deploy grace period where an absent tool is expected. Every name this preflight checks already exists, published, or the run stops. A control is never checked either. It reaches SLNG as a curated capability you attach to the agent in the dashboard, and the compiled body carries no reference to it, so there is nothing for a check to resolve.
A builtin: reference carries the tool file’s own name, not the builtin id it selects. A slng: reference carries the hosted name you wrote, so its local file name may differ. tools/hang_up.yaml declaring builtin: end_call emits a reference to hang_up, which your organisation has never heard of. The fix is to rename the file; the preflight says so.

When a check cannot be made

Two different things can go wrong here, and they are not treated the same. An old voiceai stops the run before any account read, because it cannot make the checked, resolved push this command promises. It is refused with upgrade guidance rather than falling back to a push that resolves and attaches whatever is newest, unchecked. See Deploy to SLNG. A read that fails once the run is under way splits by what the rest of the run still covers, and it splits per requirement rather than per listing. Each one says what covers it:
An unreadable vault does not block a credential your package declares, because the push reads the vault again before it writes and refuses a missing name. Two limits on that, both worth knowing:
  • The push checks a name and its kind. It never reads whether the entry holds a value, so neither run can tell you that.
  • The push reads the names in your package: the {{$NAME}} tokens in your prompts and the credentials on your own tool bodies. A credential discovered from a hosted tool’s published contract, or from a hosted MCP server’s connection, is in neither, so it reaches no later check and an unreadable vault blocks it here.
A read this command’s own promise depends on is the same case. unmute deploy attaches the published version and the MCP schema hash it resolved, under a mode that tells the push to check neither itself, so a read that failed here has no later step to catch it:
A read that could not be made is never reported as satisfied, either way. Whether it also stops the run depends on whether anything downstream still checks what it would have covered.

Creating missing secrets

Secrets are the only thing unmute can write, so they are the only gap it offers to close:
Say no and the run stops with the entry still missing. Say yes and one of two things happens. A value already in your package’s .env is piped to voiceai secret create on standard input. If there is none, the terminal is handed to voiceai secret create, which prompts with the input masked. Either way the value never reaches a command line, a file unmute writes, or your screen. There is no --value flag anywhere in this path, deliberately: an argument lands in shell history and is visible in ps. A run with no terminal, such as CI, prompts for nothing. It prints the command that would fix each entry and exits non-zero. An entry that exists under the other kind, a variable where you need a secret, is reported as a mismatch and is not offered a fill: the name is taken, so creating it again would be refused.

After a successful push

The run reports attached numbers from your organisation’s SIP trunks. It does not verify the carrier’s routing or place an inbound test call:
Or, far more often on a first deploy, that none does yet:
When a trunk is free and you are at a terminal, the run offers to point one at the agent it just deployed:
That is a single-field PATCH on the agent, so it disturbs nothing else, and it runs after the push, so re-running a deploy offers it again rather than leaving you with a silent number. Anything other than a listed number leaves it unattached, and a run with no terminal never asks and never attaches: a deploy that quietly claimed a phone number would be somebody’s phone bill. Unmute buys no numbers and configures no carrier routing. Set up the connection in SLNG and route the carrier number to its SIP destination. Attachment alone does not redirect a number from Twilio Dev Phone or another webhook. Follow Receive phone calls, then confirm a real call appears in SLNG. Required injected inputs need valid defaults for inbound calls, since the carrier supplies no web-session arguments. --call places one outbound call from the agent you just deployed, which is how you hear a phone agent without waiting for someone to ring it:
It rings a real phone and costs a real call, so it happens only when you ask. A call that fails does not fail the deploy: the agent is live either way.

A push replaces

Updating an agent replaces it with what the package declares. A tool reference the package no longer names is detached, and a field that differs from the live agent is overwritten. --dry-run names both, and changes nothing:
A preview names a version without the word attached, because nothing was: v<n>, from v<m> is a reference the agent already has at an older version, and v<n>, new is one it does not have at all. A first deployment shows no previous version rather than inventing one. The indented lines are what a replacement would alter on that attachment. They exist for the settings only the dashboard could have added: a description typed there, an invocation switched to system, a call_start trigger, a system argument, or an argument override the package no longer supplies. Each is named against the attachment it belongs to rather than left for the push to discard silently. A setting the package can declare is compared, not listed. The sentence spoken before a tool runs is announce: in the tool file, so a package whose announcement already matches the agent’s shows no line for it, and one that differs shows the sentence being replaced. Only the parts of that setting a package has no key for, such as making the agent wait for the sentence, are reported as things a replacement clears. A detached reference is named by the identifier the live agent carries for it, because a tool the package no longer mentions may be knowable by nothing else. The published-description and published-parameter lines say the two versions’ contracts differ. They say nothing about whether the tool behaves the same: a schema comparison cannot see a change that kept the same signature, and this output does not pretend otherwise. An agent’s name comes from name: in agent.yaml joined to the target, so a run that resolves to update when you expected create means an agent of that name already exists. unmute deploy warns and names it; change name:, or let --agent-id pick a different one.

When it refuses

A refusal blocks the agent push. A real deploy may already have completed MCP refresh or consented Vault writes; inspect deploy-report.json for those changes. A dry run changes no remote state. The problems you will meet most: These problems stop the agent push. no mirror of it is committed and does not match the hash are not on this list. unmute deploy compiles only the slng target, which reads no mirror. Those two refusals belong to a livekit or pipecat compile of the same package. See Hosted tools.

Trying a hosted tool

No sample is needed to deploy references to published tools. To exercise one separately, use the same account and supply arguments matching its contract:
This executes the hosted tool against its real dependencies. The example assumes your organisation publishes check_order with an order_number parameter; change the name and input for your own tool.

Where to go next

Test the deployed agent

Test in the browser, configure phone routing, and read call results.