Defining your own slash commands¶
Beyond the built-in slash commands, you can define your own in Lua. A user command can expand into a canned prompt (optionally with arguments), fill the input box for you to edit, or kick off a detached sub-agent that runs alongside your interactive session.
A discover_commands handler runs
when your config loads and again on every /reload, with a
CommandRegistry to add commands
to, so editing your config and reloading adds or removes a command without
restarting the process:
agent.on('discover_commands', function(registry)
registry:add {
name = 'pr',
about = 'Review a GitHub PR',
aliases = { 'pull' },
args = { { name = 'number', required = true, help = 'the PR number' } },
run = function(ctx, args)
return ctx:submit_prompt('Review PR #' .. args.number)
end,
}
end)
That registers /pr <number> (and the alias /pull). The command appears in
/help and tab-completion, and /pr --help prints its own usage. Typing /pr
1234 runs the handler, which submits Review PR #1234 as a turn just as if
you had typed it.
Built-in commands always win: a user command whose name or an alias collides
with a built-in verb (or another user command) is dropped with a warning in
scrollback, so a config can never shadow /quit or /model.
Arguments¶
Each entry in args becomes a
clap argument, so validation, --help, and completion
all follow from what you declare. An argument is positional unless you give it
a long or short, which makes it a --named option instead:
name-- the key the parsed value arrives under in the handler'sargstable.help-- the description shown in/<cmd> --help.required-- whether the argument must be supplied (positional or option).long/short-- the--longname or-scharacter; either one makes the argument a named option rather than positional.flag-- a valueless switch (--verbose), delivered to the handler as a boolean:truewhen present,falseotherwise.multiple-- accepts more than one value, delivered as a list of strings.
Parsed values reach the handler keyed by each argument's name: a string for a
single value, a list of strings for multiple, true/false for a flag,
and nil for an optional argument that was not supplied.
What a handler returns¶
The handler receives a
CommandCtx and the parsed args
table. Its methods each build one
CommandAction; the handler returns
the single action it wants performed, or nil to do nothing.
ctx:submit_prompt(text, opts?) submits text as a user turn. An optional
opts applies a model and/or personality override for just that turn,
reverting afterwards:
run = function(ctx, _args)
return ctx:submit_prompt('go', {
model = 'anthropic/claude-opus-4-8',
personality = 'review-panel/Execute',
})
end,
ctx:set_input(text) fills the input box with text for you to edit and send
yourself, rather than submitting it. Useful for a command that drafts a prompt
you want to tweak first.
Background sub-agents¶
ctx:spawn_subagent{ ... } launches a detached sub-agent that runs alongside
your interactive session, so you can reach a checkpoint, kick off longer work,
and keep going in the main session while it runs:
registry:add {
name = 'panel',
about = 'Kick off the review panel in the background',
run = function(ctx, _args)
return ctx:spawn_subagent {
personality = 'review-panel',
mode = 'Execute',
task = 'review the current branch',
context = 'fork',
}
end,
}
personality-- the personality the sub-agent runs as.mode-- the personality's mode to run in; its first when omitted.task-- the prompt the sub-agent starts from.context-- how its conversation is seeded:fresh(the default) starts it with only itstask;forkforks the current conversation at this point, so the sub-agent sees the same history you do. The fork is captured when the command runs, so it reflects the checkpoint rather than a later turn.
The run is headless: it reports only its completion in scrollback, with a wa
--resume <session> pointer to its own transcript on disk. While it runs it is
listed by /jobs, can be cancelled with /kill, and
holds back a plain /quit (which reports the outstanding work; /quit --force
cancels and quits).
Keeping commands tidy¶
As with custom tools, a command is best kept in a
self-contained module alongside your config (e.g.
~/.config/wallah/commands.lua) that you pull in with require('commands'),
so it reads and reuses independently of the rest of your config.