refactor(agent): extract reusable tool loop and make subagents independent
Extract core LLM tool loop logic into shared RunToolLoop function that can be used by both main agent and subagents. Subagents now run their own tool loop with dedicated tool registry, enabling full independence. Key changes: - New pkg/tools/toolloop.go with reusable tool execution logic - Subagents use message tool to communicate directly with users - Heartbeat processing is now stateless via ProcessHeartbeat - Simplified system message routing without result forwarding - Shared tool registry creation for consistency between agents This architecture follows openclaw's design where async tools notify via bus and subagents handle their own user communication.
This commit is contained in:
@@ -28,6 +28,8 @@ type SubagentManager struct {
|
||||
defaultModel string
|
||||
bus *bus.MessageBus
|
||||
workspace string
|
||||
tools *ToolRegistry
|
||||
maxIterations int
|
||||
nextID int
|
||||
}
|
||||
|
||||
@@ -38,10 +40,27 @@ func NewSubagentManager(provider providers.LLMProvider, defaultModel, workspace
|
||||
defaultModel: defaultModel,
|
||||
bus: bus,
|
||||
workspace: workspace,
|
||||
tools: NewToolRegistry(),
|
||||
maxIterations: 10,
|
||||
nextID: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// SetTools sets the tool registry for subagent execution.
|
||||
// If not set, subagent will have access to the provided tools.
|
||||
func (sm *SubagentManager) SetTools(tools *ToolRegistry) {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
sm.tools = tools
|
||||
}
|
||||
|
||||
// RegisterTool registers a tool for subagent execution.
|
||||
func (sm *SubagentManager) RegisterTool(tool Tool) {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
sm.tools.Register(tool)
|
||||
}
|
||||
|
||||
func (sm *SubagentManager) Spawn(ctx context.Context, task, label, originChannel, originChatID string, callback AsyncCallback) (string, error) {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
@@ -73,10 +92,15 @@ func (sm *SubagentManager) runTask(ctx context.Context, task *SubagentTask, call
|
||||
task.Status = "running"
|
||||
task.Created = time.Now().UnixMilli()
|
||||
|
||||
// Build system prompt for subagent
|
||||
systemPrompt := `You are a subagent. Complete the given task independently and report the result.
|
||||
You have access to tools - use them as needed to complete your task.
|
||||
After completing the task, provide a clear summary of what was done.`
|
||||
|
||||
messages := []providers.Message{
|
||||
{
|
||||
Role: "system",
|
||||
Content: "You are a subagent. Complete the given task independently and report the result.",
|
||||
Content: systemPrompt,
|
||||
},
|
||||
{
|
||||
Role: "user",
|
||||
@@ -95,9 +119,22 @@ func (sm *SubagentManager) runTask(ctx context.Context, task *SubagentTask, call
|
||||
default:
|
||||
}
|
||||
|
||||
response, err := sm.provider.Chat(ctx, messages, nil, sm.defaultModel, map[string]interface{}{
|
||||
"max_tokens": 4096,
|
||||
})
|
||||
// Run tool loop with access to tools
|
||||
sm.mu.RLock()
|
||||
tools := sm.tools
|
||||
maxIter := sm.maxIterations
|
||||
sm.mu.RUnlock()
|
||||
|
||||
loopResult, err := RunToolLoop(ctx, ToolLoopConfig{
|
||||
Provider: sm.provider,
|
||||
Model: sm.defaultModel,
|
||||
Tools: tools,
|
||||
MaxIterations: maxIter,
|
||||
LLMOptions: map[string]any{
|
||||
"max_tokens": 4096,
|
||||
"temperature": 0.7,
|
||||
},
|
||||
}, messages, task.OriginChannel, task.OriginChatID)
|
||||
|
||||
sm.mu.Lock()
|
||||
var result *ToolResult
|
||||
@@ -127,10 +164,10 @@ func (sm *SubagentManager) runTask(ctx context.Context, task *SubagentTask, call
|
||||
}
|
||||
} else {
|
||||
task.Status = "completed"
|
||||
task.Result = response.Content
|
||||
task.Result = loopResult.Content
|
||||
result = &ToolResult{
|
||||
ForLLM: fmt.Sprintf("Subagent '%s' completed: %s", task.Label, response.Content),
|
||||
ForUser: response.Content,
|
||||
ForLLM: fmt.Sprintf("Subagent '%s' completed (iterations: %d): %s", task.Label, loopResult.Iterations, loopResult.Content),
|
||||
ForUser: loopResult.Content,
|
||||
Silent: false,
|
||||
IsError: false,
|
||||
Async: false,
|
||||
|
||||
Reference in New Issue
Block a user