Slash commands¶
A slash command is text that starts with / and triggers a registered handler before (or instead of) the model. Maven supports three sources:
- Built-in slash commands in
kernel/slash(e.g./compact) and gateway-registered commands (e.g./reload,/status). - Plugin-contributed slash commands via the
SlashPluginaxis (e.g./cron-add,/cron-list,/cron-remove). - Workspace-defined Telegram slash commands loaded from
<workspace>/.telegram/slashes/*.md.
A single inbound supports one slash invocation per turn. Multiple / lines return an error.
Parsing¶
The lexer mirrors agentsdk-go/pkg/runtime/commands:
- Quotes:
",'. Backslash escapes one character. - Long flags:
--key=valueor--key value(space-separated when the next token does not start with-). - Bare arguments after the command name accumulate into
Args.
Dispatch¶
slash.PreTurn(ctx, registry, input) parses, looks up the handler, and runs it:
- Empty
Result.Output⇒ the turn continues to the model.Result.Metadatais merged into the model request (enriched withslash.turn.channelandslash.turn.chat_id).Result.PostActionis queued for post-response handling. - Non-empty
Result.Output⇒ skipped model run; the trimmed output is the user reply.
The dispatcher applies an optional expected slash name filter: channels that parsed /command themselves (like Telegram's workspace slashes) set ExpectedSlashName so PreTurn only fires when the parsed name matches; otherwise it falls through to the model.
On Telegram, kernel and plugin slashes are registered in the BotFather menu automatically on gateway start (merged with workspace defs under .telegram/slashes/; workspace overrides description and handling when names collide).
Built-in: /reload¶
/reload re-runs Gateway.Apply without restarting the process. Use it after editing AGENTS.md, SOUL.md, memory/MEMORY.md, or skills under skills/ — the config watcher only watches config.json.
The handler replies Reloading… immediately; the reload runs on the gateway main loop (same path as hot reload). Works with or without gateway.hotReload.
Built-in: /status¶
/status prints cron job counts and MEMORY.md size. No model turn.
Built-in: /compact¶
/compact [free-form focus text] compresses the current conversation into a continuation summary and rotates the chat session. Implementation: internal/kernel/slash/builtin_compact.go.
Flow:
- The handler emits a
Metadata["api.prepend_prompt"]instructing the model to produce a continuation summary (no user-facing fluff). - The model runs and returns the summary.
- The post-action handler (
postaction.HandlePostResponse) sees aCompactRotateAction: - Optional pre-compact flush hook fires (the gateway wires
rememberhere so important context lands in long-term memory before the rotation). - The router rotates the session.
- The summary is seeded as a
systemmessage into the new session's history file (<workspace>/.maven/history/<sessionID>.json). - Reply is either the summary or a fixed ack ("Conversation compacted and continued in a fresh session.") depending on response mode.
Built-in: /new¶
Routing hint, not a registered slash. Channels emit bus.RoutingHints.BuiltinCommand = "new". The pipeline calls Router.Rotate(routeKey) and replies "Started a fresh session." — the model is never invoked. Telegram emits this from a top-level handler so users can run /new even without a workspace slash def.
Plugin slash: cron¶
The cron plugin contributes three slash commands:
| Command | Description |
|---|---|
/cron-add |
Schedule a persisted job. Exactly one of --expr, --in, or --at-ms. With --deliver true plus --channel / --to, sends the job result to a chat. |
/cron-list |
Print all persisted jobs (id, schedule, delivery). |
/cron-remove --id <id> |
Remove a job by id. |
Examples:
/cron-add --name water --in 30m --message "Remind me to drink water"
/cron-add --name standup --expr "0 30 9 * * MON-FRI" --message "Daily standup time" --deliver true --channel telegram --to 42
/cron-list
/cron-remove --id 7d1a0c…
See Guides: Cron jobs for the field reference.
Workspace Telegram slashes¶
Drop a markdown file under <workspace>/.telegram/slashes/:
---
command: compact
description: Compress conversation history
type: pipeline
passthrough: true
streaming: false
---
| Frontmatter field | Type | Default | Meaning |
|---|---|---|---|
command |
string | required | Slash name without leading /. |
description |
string | "" |
Shown in Telegram's BotFather command list (truncated to 256 chars). |
type |
enum | agent |
local (script handler), agent (model run with prompt or args), pipeline (model run via the kernel slash dispatch). |
session |
enum | current |
current or isolated. Local commands must use current. |
handler |
string | "" |
Script name under .telegram/handlers/ (local type only). |
passthrough |
bool | false |
Pipeline-only. When true, sends /cmd args straight to the dispatcher instead of substituting a prompt body. |
streaming |
bool | true |
Whether the gateway uses the streaming pipeline when this slash fires. |
The body becomes the agent prompt (when passthrough: false). Use this for "prompt presets":
---
command: brief
description: Summarize today's events
type: agent
session: current
---
Summarize what happened today based on my memory and notes. Keep it under 100 words.
For local, Maven shells out to .telegram/handlers/<handler> with (session_id, args) and WORKSPACE / SESSION_ID env vars. Stdout becomes the reply.
Adding a slash command in a plugin¶
// internal/plugins/yourplugin/slash.go
func (p *Plugin) SlashCommands(*config.Config) []plugin.SlashCommand {
return []plugin.SlashCommand{
{
Definition: plugin.SlashDefinition{
Name: "ping",
Description: "Replies with pong.",
},
Handler: handlerFunc(func(ctx context.Context, inv plugin.SlashInvocation) (plugin.SlashResult, error) {
return plugin.SlashResult{Command: "ping", Output: "pong"}, nil
}),
},
}
}
Then implement SlashPlugin on the plugin struct and add it to the registry in wire.go. The gateway merges these with built-ins on every Apply.
Errors¶
- Parse error (unclosed quote, dangling escape, invalid name) → user sees the command-error template.
- Handler error → same template, logged at error level.
- Multiple commands in one inbound → error.
- Duplicate registration (two plugins claim the same name) → fatal at gateway start.