-
Notifications
You must be signed in to change notification settings - Fork 360
feat: add slash commands for agent switching #2790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ebab2f8
8e82e94
7e3d57d
a8dfd3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| #!/usr/bin/env docker agent run | ||
|
|
||
| # This example demonstrates slash commands that switch the active agent. | ||
| # Type "/plan" in the TUI to hand the conversation off to the planner sub-agent, | ||
| # "/review" to switch to the reviewer, and "/back" to return to the root agent. | ||
| # | ||
| # Anything typed after the slash command (e.g. "/plan add a logout button") is | ||
| # forwarded as the first user prompt to the target agent. | ||
|
|
||
| models: | ||
| claude: | ||
| provider: anthropic | ||
| model: claude-sonnet-4-5 | ||
|
|
||
| agents: | ||
| root: | ||
| model: claude | ||
| description: The default agent. Implements the work once a plan exists. | ||
| instruction: | | ||
| You are the implementation agent. You write and edit code. | ||
| If the user asks for a plan or design discussion, use the /plan command | ||
| to switch to the planner sub-agent. | ||
| sub_agents: | ||
| - planner | ||
| - reviewer | ||
| toolsets: | ||
| - type: filesystem | ||
| - type: shell | ||
| commands: | ||
| plan: | ||
| description: "Hand off to the planner sub-agent" | ||
| agent: planner | ||
| review: | ||
| description: "Hand off to the reviewer sub-agent" | ||
| agent: reviewer | ||
|
|
||
| planner: | ||
| model: claude | ||
| description: Plans the work before implementation. | ||
| instruction: | | ||
| You are the planning agent. Ask clarifying questions, then produce a | ||
| step-by-step plan in markdown. Do not write code. | ||
| sub_agents: | ||
| - root | ||
| commands: | ||
| back: | ||
| description: "Return to the root agent" | ||
| agent: root | ||
|
|
||
| reviewer: | ||
| model: claude | ||
| description: Reviews local changes for quality and security. | ||
| instruction: | | ||
| You review the local Git changes and provide concise, actionable feedback | ||
| on code quality, security, and maintainability. | ||
| sub_agents: | ||
| - root | ||
| toolsets: | ||
| - type: shell | ||
| commands: | ||
| back: | ||
| description: "Return to the root agent" | ||
| agent: root |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -326,9 +326,21 @@ func Run(ctx context.Context, out *Printer, cfg Config, rt runtime.Runtime, sess | |
| // attachment was used). Callers should pass that path to | ||
| // session.Session.AddAttachedFile so sub-agents inherit the file context. | ||
| func PrepareUserMessage(ctx context.Context, rt runtime.Runtime, userInput, globalAttachPath string) (*session.Message, string) { | ||
| // Resolve any /command to its prompt text | ||
| // Resolve any /command to its prompt text BEFORE switching agents. | ||
| // This ensures the command is looked up in the original agent's command table. | ||
| resolvedContent := runtime.ResolveCommand(ctx, rt, userInput) | ||
|
|
||
| // Switch the active agent if the /command targets a sub-agent. | ||
| // This must happen before the message is added to the session so the | ||
| // next runtime turn runs on the right agent. | ||
| if cmd, _, ok := runtime.LookupCommand(ctx, rt, userInput); ok && cmd.Agent != "" { | ||
| // If the agent switch fails, we must not proceed with sending the message | ||
| // to the wrong agent. Return an empty message to signal the error. | ||
| if err := rt.SetCurrentAgent(cmd.Agent); err != nil { | ||
|
dgageot marked this conversation as resolved.
|
||
| slog.WarnContext(ctx, "Failed to switch agent for /command", "agent", cmd.Agent, "error", err) | ||
| return session.UserMessage(""), "" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [HIGH] Empty message silently added to session and forwarded to LLM on agent-switch failure
userMsg, attachedPath := PrepareUserMessage(ctx, rt, userInput, cfg.AttachmentPath)
sess.AddMessage(userMsg) // no guard — empty message is added
rt.RunStream(ctx, sess) // LLM receives empty user message in historyThere is no nil/empty check at the call site. The LLM will receive a blank Suggested fix — return func PrepareUserMessage(...) (*session.Message, string, error) {
...
if err := rt.SetCurrentAgent(cmd.Agent); err != nil {
slog.WarnContext(ctx, "Failed to switch agent for /command", ...)
return nil, "", fmt.Errorf("switch agent %q: %w", cmd.Agent, err)
}
...
return msg, attachPath, nil
}Then the caller can |
||
| } | ||
|
dgageot marked this conversation as resolved.
|
||
| } | ||
|
dgageot marked this conversation as resolved.
|
||
| // Parse for /attach commands in the message | ||
| messageText, attachPath := ParseAttachCommand(resolvedContent) | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.