You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
4.1 KiB
102 lines
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
|
|
|
|
1. The parent agent has a `task` tool. The subagent gets all base tools except `task` (no recursive spawning).
|
|
|
|
```java
|
|
// 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();
|
|
```
|
|
|
|
2. The subagent starts with a brand new `ChatClient` and an independent context. Only the final text returns to the parent.
|
|
|
|
```java
|
|
@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
|
|
|
|
```sh
|
|
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):
|
|
|
|
1. `Use a subtask to find what testing framework this project uses`
|
|
2. `Delegate: read all .java files and summarize what each one does`
|
|
3. `Use a task to create a new module, then verify it from here`
|
|
|