Advanced 19 - Slash commands: built-ins and templates
The chat composer's /<word> palette mixes two different things:
a fixed set of client-side commands every app gets for free
(help, model switch, compact, stop, …), and app-declared
templates under ui.slash_commands: in the YAML. There is no
third, server-dispatched "builtin" kind - the daemon never parses
ui.slash_commands at all; only the chat client does.
The two real flavours
| Flavour | Declared with | Handled by | LLM cost | Persisted in history |
|---|---|---|---|---|
| Client built-in | Nothing - fixed, ships with the client | The chat client itself (opens a panel, calls a REST endpoint, or updates local state) | 0 | No |
| Template | command / description / template under ui.slash_commands: | Chat client fills {{var}} and sends the rendered text as a normal message | 1 normal turn | Yes (as user + assistant message) |
ui.slash_commands entries only ever carry command,
description, and template - the client reads nothing else from
an entry. There's no way to make an app-declared command run
server-side without an LLM turn; that's what the client built-ins
below are for, and their list is fixed, not YAML-configurable.
The client built-ins
Every app gets these for free (some are hidden when the feature
they open is unavailable - e.g. /tools disappears if
ui.features.tools_panel is off):
| Command | What it does |
|---|---|
/help | Show the list of built-in commands |
/use_skill | Pick a skill to hand the agent for this message |
/model | Switch the model (and reasoning effort) per agent |
/compact | Compact the conversation context (calls the real .../sessions/{id}/compact endpoint) |
/stop | Stop the current response |
/new | Start a new session |
/context | Open the context window panel |
/activity | Open the Activity panel (sub-agents + tasks) |
/tools | Open the Tools panel |
/tasks | Open the Background tasks panel |
/snippets | Open the Snippets panel |
None of these are declared in app.yaml - they exist purely in the
chat client and are the same across every app.
The YAML - app-declared templates
app:
app_id: tuto-slash-commands
name: Tuto - Slash Commands
version: "1.0"
runtime:
mode: conversation
workdir_mode: none
max_turns: 4
timeout: 60
tool_injection: direct
agents:
- id: main
role: assistant
brain:
provider: openai
backend: openai_compat
model: gpt-5-mini
config:
api_key: placeholder
base_url: https://api.openai.com/v1
temperature: 0.5
max_tokens: 1024
system_prompt: |
You are a friendly assistant. When asked to greet
someone, respond with brief greetings in multiple
languages. When asked to summarise a text, produce a
one-paragraph summary.
tools:
modules: {}
capabilities:
default_policy: auto
max_risk_level: low
# Variables referenced by ``{{name}}`` / ``{{text}}`` in
# template slash commands MUST be declared here, otherwise
# the compiler rejects the YAML with
# "placeholder references undefined variable".
dev:
variables:
name: "there"
text: ""
ui:
slash_commands:
- command: /greet
description: Greet someone in 3 languages.
template: |
Reply with a short greeting for {{name}} in three
different languages. One line per language, format:
`<language>: <greeting>`.
- command: /summarise
description: One-paragraph summary of a piece of text.
template: |
Summarise the following text in one paragraph
(5-8 sentences max), keeping the most important
facts:
{{text}}
Three things to know:
- Path matters.
slash_commands:lives underui:, not underruntime:orapp:. Putting it elsewhere silently registers nothing. - Variables must be declared. Any
{{var}}in a template must have a matching key indev.variables. The declared value is the default; the chat client supplies the real value when the user fills the form. Without the declaration the compiler rejects the YAML withplaceholder '{{var}}' references undefined variable. - Variable values are strings.
dev.variables.name: "there"works;name: {description: ..., default: ...}fails with "schema error at 'dev.variables.name': Input should be a valid string".
Sample flow: template /greet (client-side fill)
When the user types /greet Paul in the chat composer,
the client looks up the template, fills {{name}} with
"Paul", and POSTs the rendered string as a regular user
message. The daemon never sees the slash form.
digitorn install tuto-slash-commands.yaml
digitorn chat tuto-slash-commands
# type: /greet Paul
The composer expands the template and the agent replies:
[user] Reply with a short greeting for Paul in three
different languages. One line per language, format:
`<language>: <greeting>`.
[assistant]
English: Hi Paul!
Spanish: ¡Hola, Paul!
French: Salut Paul!
Both the user and assistant turns are persisted in the session message history, exactly like any other turn.
When to reach for a template command
Use a ui.slash_commands template when the LLM does the work but
the prompt follows a stable pattern: /commit <msg>,
/translate <lang> <text>, /code-review <file>,
/explain <symbol>. The client form-fills the variables before
sending; the agent gets a clean instruction without the user
typing the boilerplate.
Reach for one of the client built-ins (or ask for a new one) when the action doesn't need the LLM at all - there's no way to add a new no-LLM command from an app's own YAML today.
Going further
- Combine with skills: declare an app skill under
skills:and invoke it via the runtime's/use_skill <name>shortcut (different mechanism, see Advanced 2). - Let users author their own templates per-app: set
dev.allow_user_skills: trueso the chat composer's skill picker stores user-defined entries inuser_skills(per-user, per-app).