mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
fix(chatd): improve subagent tool descriptions and strip tools from child agents (#22441)
Two changes:
1. **Gate subagent tools behind `!chat.ParentChatID.Valid`** so child
agents never receive `spawn_agent`, `wait_agent`, `message_agent`, or
`close_agent`. Previously all 4 tools were given to every chat.
`spawn_agent` would fail at runtime ("delegated chats cannot create
child subagents") but the other 3 had no guard at all — meaning a child
could theoretically operate on sibling chats. Removing the tools
entirely is cleaner and saves context window.
2. **Rewrite tool descriptions to explain *when* to use them**, not just
what they do. `spawn_agent` now says to use it for clearly scoped,
independent, self-contained tasks (e.g. fixing a specific bug, writing a
single module, running a migration) and explicitly says *not* to use it
for simple operations you can handle with
`execute`/`read_file`/`write_file`. It also states that child agents
cannot spawn their own subagents. The other 3 tools get similar
guidance-oriented descriptions.
Co-authored-by: Coder <coder@users.noreply.github.com>
This commit is contained in:
@@ -2093,9 +2093,14 @@ func (p *Server) runChat(
|
||||
GetWorkspaceConn: getWorkspaceConn,
|
||||
}),
|
||||
}
|
||||
tools = append(tools, p.subagentTools(func() database.Chat {
|
||||
return chat
|
||||
})...)
|
||||
// Only root chats (not delegated subagents) get subagent tools.
|
||||
// Child agents must not spawn further subagents — they should
|
||||
// focus on completing their delegated task.
|
||||
if !chat.ParentChatID.Valid {
|
||||
tools = append(tools, p.subagentTools(func() database.Chat {
|
||||
return chat
|
||||
})...)
|
||||
}
|
||||
|
||||
_, err = chatloop.Run(ctx, chatloop.RunOptions{
|
||||
Model: model,
|
||||
|
||||
@@ -46,7 +46,15 @@ func (p *Server) subagentTools(currentChat func() database.Chat) []fantasy.Agent
|
||||
return []fantasy.AgentTool{
|
||||
fantasy.NewAgentTool(
|
||||
"spawn_agent",
|
||||
"Spawn a delegated child agent chat from the root chat.",
|
||||
"Spawn a delegated child agent to work on a clearly scoped, "+
|
||||
"independent task in parallel. Use this when the task is "+
|
||||
"self-contained and would benefit from a separate agent "+
|
||||
"(e.g. fixing a specific bug, writing a single module, "+
|
||||
"running a migration). Do NOT use for simple or quick "+
|
||||
"operations you can handle directly with execute, "+
|
||||
"read_file, or write_file. The child agent receives the "+
|
||||
"same workspace tools but cannot spawn its own subagents. "+
|
||||
"After spawning, use wait_agent to collect the result.",
|
||||
func(ctx context.Context, args spawnAgentArgs, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
|
||||
if currentChat == nil {
|
||||
return fantasy.NewTextErrorResponse("subagent callbacks are not configured"), nil
|
||||
@@ -80,7 +88,10 @@ func (p *Server) subagentTools(currentChat func() database.Chat) []fantasy.Agent
|
||||
),
|
||||
fantasy.NewAgentTool(
|
||||
"wait_agent",
|
||||
"Wait until a delegated descendant agent reaches a non-streaming status.",
|
||||
"Wait until a spawned child agent finishes its task. "+
|
||||
"Returns the agent's final response and status. "+
|
||||
"Call this after spawn_agent to collect the result "+
|
||||
"before continuing your own work.",
|
||||
func(ctx context.Context, args waitAgentArgs, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
|
||||
if currentChat == nil {
|
||||
return fantasy.NewTextErrorResponse("subagent callbacks are not configured"), nil
|
||||
@@ -117,7 +128,11 @@ func (p *Server) subagentTools(currentChat func() database.Chat) []fantasy.Agent
|
||||
),
|
||||
fantasy.NewAgentTool(
|
||||
"message_agent",
|
||||
"Send a message to a delegated descendant agent. Use wait_agent to collect a response.",
|
||||
"Send a follow-up message to a previously spawned child "+
|
||||
"agent. Use this to provide additional instructions, "+
|
||||
"corrections, or context to a running or completed "+
|
||||
"agent. After sending, use wait_agent to collect the "+
|
||||
"updated response.",
|
||||
func(ctx context.Context, args messageAgentArgs, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
|
||||
if currentChat == nil {
|
||||
return fantasy.NewTextErrorResponse("subagent callbacks are not configured"), nil
|
||||
@@ -154,7 +169,9 @@ func (p *Server) subagentTools(currentChat func() database.Chat) []fantasy.Agent
|
||||
),
|
||||
fantasy.NewAgentTool(
|
||||
"close_agent",
|
||||
"Interrupt a delegated descendant agent immediately.",
|
||||
"Immediately stop a spawned child agent. Use this to "+
|
||||
"cancel a subagent that is stuck, no longer needed, "+
|
||||
"or working on the wrong approach.",
|
||||
func(ctx context.Context, args closeAgentArgs, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
|
||||
if currentChat == nil {
|
||||
return fantasy.NewTextErrorResponse("subagent callbacks are not configured"), nil
|
||||
|
||||
Reference in New Issue
Block a user