Skip to main content

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​

FlavourDeclared withHandled byLLM costPersisted in history
Client built-inNothing - fixed, ships with the clientThe chat client itself (opens a panel, calls a REST endpoint, or updates local state)0No
Templatecommand / description / template under ui.slash_commands:Chat client fills {{var}} and sends the rendered text as a normal message1 normal turnYes (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):

CommandWhat it does
/helpShow the list of built-in commands
/use_skillPick a skill to hand the agent for this message
/modelSwitch the model (and reasoning effort) per agent
/compactCompact the conversation context (calls the real .../sessions/{id}/compact endpoint)
/stopStop the current response
/newStart a new session
/contextOpen the context window panel
/activityOpen the Activity panel (sub-agents + tasks)
/toolsOpen the Tools panel
/tasksOpen the Background tasks panel
/snippetsOpen 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.yaml
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 under ui:, not under runtime: or app:. Putting it elsewhere silently registers nothing.
  • Variables must be declared. Any {{var}} in a template must have a matching key in dev.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 with placeholder '{{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.

bash
digitorn install tuto-slash-commands.yaml
digitorn chat tuto-slash-commands
# type: /greet Paul

The composer expands the template and the agent replies:

text
[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: true so the chat composer's skill picker stores user-defined entries in user_skills (per-user, per-app).