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:
yinwm
2026-02-13 14:39:39 +08:00
parent 4dfa133cb8
commit 0cce9fc905
5 changed files with 319 additions and 57 deletions

View File

@@ -663,14 +663,17 @@ func gatewayCmd() {
if channel == "" || chatID == "" {
channel, chatID = "cli", "direct"
}
response, err := agentLoop.ProcessDirectWithChannel(context.Background(), prompt, "heartbeat", channel, chatID)
// Use ProcessHeartbeat - no session history, each heartbeat is independent
response, err := agentLoop.ProcessHeartbeat(context.Background(), prompt, channel, chatID)
if err != nil {
return tools.ErrorResult(fmt.Sprintf("Heartbeat error: %v", err))
}
if response == "HEARTBEAT_OK" {
return tools.SilentResult("Heartbeat OK")
}
return tools.UserResult(response)
// For heartbeat, always return silent - the subagent result will be
// sent to user via processSystemMessage when the async task completes
return tools.SilentResult(response)
})
channelManager, err := channels.NewManager(cfg, msgBus)