4.1 KiB
s04: Subagents
s01 > s02 > s03 > [ s04 ] s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12
"Break big tasks down; each subtask gets a clean context" -- subagents use independent messages[], keeping the main conversation clean.
Harness layer: Context isolation -- protecting the model's clarity of thought.
Problem
As the agent works, its messages array grows. Every file read, every bash output stays in context permanently. "What testing framework does this project use?" might require reading 5 files, but the parent only needs one word: "pytest."
Solution
Parent agent Subagent
+------------------+ +------------------+
| messages=[...] | | messages=[] | <-- fresh
| | dispatch | |
| tool: task | ----------> | while tool_use: |
| prompt="..." | | call tools |
| | summary | append results |
| result = "..." | <---------- | return last text |
+------------------+ +------------------+
Parent context stays clean. Subagent context is discarded.
How It Works
- The parent agent has a
tasktool. The subagent gets all base tools excepttask(no recursive spawning).
// Parent Agent: has base tools + SubagentTool
this.chatClient = ChatClient.builder(chatModel)
.defaultSystem("You are a coding agent. "
+ "Use the task tool to delegate subtasks.")
.defaultTools(
new BashTool(),
new ReadFileTool(),
new WriteFileTool(),
new EditFileTool(),
new SubagentTool(chatModel) // Parent Agent exclusive
)
.build();
- The subagent starts with a brand new
ChatClientand an independent context. Only the final text returns to the parent.
@Tool(description = "Spawn a subagent with fresh context. "
+ "Use for exploration or subtasks that might pollute the main context.")
public String task(
@ToolParam(description = "The task prompt") String prompt,
@ToolParam(description = "Short description", required = false)
String description) {
// Create a brand new ChatClient -- this IS "context isolation"
ChatClient subClient = ChatClient.builder(chatModel)
.defaultSystem("You are a coding subagent. "
+ "Complete the task, then summarize findings.")
.defaultTools( // Base tools, no task (prevents recursion)
new BashTool(),
new ReadFileTool(),
new WriteFileTool(),
new EditFileTool()
)
.build();
String result = subClient.prompt()
.user(prompt)
.call()
.content();
// Only the final text is returned; subagent context is discarded
return (result != null) ? result : "(no summary)";
}
The subagent may have run multiple tool calls, but its entire message history is discarded. The parent receives only a summary text, returned as a normal tool_result. Spring AI's ChatClient.call() manages the tool loop internally -- no need to manually limit iteration count.
What Changed From s03
| Component | Before (s03) | After (s04) |
|---|---|---|
| Tools | 5 | 5 (base) + SubagentTool (parent only) |
| Context | Single shared | Parent + child isolation (independent ChatClient) |
| Subagent | None | SubagentTool.task() method |
| Return value | N/A | Summary text only |
Try It
cd learn-claude-code
mvn exec:java -Dexec.mainClass=io.mybatis.learn.s04.S04Subagent
Try these prompts (English prompts work better with LLMs, but Chinese also works):
Use a subtask to find what testing framework this project usesDelegate: read all .java files and summarize what each one doesUse a task to create a new module, then verify it from here