2026-02-04 11:06:13 +00:00
package tools
import (
"context"
"fmt"
"sync"
"time"
2026-02-10 08:05:23 +00:00
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/providers"
2026-02-04 11:06:13 +00:00
)
type SubagentTask struct {
ID string
Task string
Label string
OriginChannel string
OriginChatID string
Status string
Result string
Created int64
}
type SubagentManager struct {
tasks map [ string ] * SubagentTask
mu sync . RWMutex
2026-02-10 08:05:23 +00:00
provider providers . LLMProvider
bus * bus . MessageBus
2026-02-04 11:06:13 +00:00
workspace string
nextID int
}
2026-02-10 08:05:23 +00:00
func NewSubagentManager ( provider providers . LLMProvider , workspace string , bus * bus . MessageBus ) * SubagentManager {
2026-02-04 11:06:13 +00:00
return & SubagentManager {
tasks : make ( map [ string ] * SubagentTask ) ,
provider : provider ,
2026-02-10 08:05:23 +00:00
bus : bus ,
2026-02-04 11:06:13 +00:00
workspace : workspace ,
nextID : 1 ,
}
}
func ( sm * SubagentManager ) Spawn ( ctx context . Context , task , label , originChannel , originChatID string ) ( string , error ) {
sm . mu . Lock ( )
defer sm . mu . Unlock ( )
taskID := fmt . Sprintf ( "subagent-%d" , sm . nextID )
sm . nextID ++
subagentTask := & SubagentTask {
ID : taskID ,
Task : task ,
Label : label ,
OriginChannel : originChannel ,
OriginChatID : originChatID ,
Status : "running" ,
Created : time . Now ( ) . UnixMilli ( ) ,
}
sm . tasks [ taskID ] = subagentTask
go sm . runTask ( ctx , subagentTask )
if label != "" {
return fmt . Sprintf ( "Spawned subagent '%s' for task: %s" , label , task ) , nil
}
return fmt . Sprintf ( "Spawned subagent for task: %s" , task ) , nil
}
func ( sm * SubagentManager ) runTask ( ctx context . Context , task * SubagentTask ) {
task . Status = "running"
task . Created = time . Now ( ) . UnixMilli ( )
2026-02-10 08:05:23 +00:00
messages := [ ] providers . Message {
2026-02-04 11:06:13 +00:00
{
Role : "system" ,
Content : "You are a subagent. Complete the given task independently and report the result." ,
} ,
{
Role : "user" ,
Content : task . Task ,
} ,
}
response , err := sm . provider . Chat ( ctx , messages , nil , sm . provider . GetDefaultModel ( ) , map [ string ] interface { } {
"max_tokens" : 4096 ,
} )
sm . mu . Lock ( )
defer sm . mu . Unlock ( )
if err != nil {
task . Status = "failed"
task . Result = fmt . Sprintf ( "Error: %v" , err )
} else {
task . Status = "completed"
task . Result = response . Content
}
2026-02-10 08:05:23 +00:00
// Send announce message back to main agent
if sm . bus != nil {
announceContent := fmt . Sprintf ( "Task '%s' completed.\n\nResult:\n%s" , task . Label , task . Result )
sm . bus . PublishInbound ( bus . InboundMessage {
Channel : "system" ,
SenderID : fmt . Sprintf ( "subagent:%s" , task . ID ) ,
// Format: "original_channel:original_chat_id" for routing back
ChatID : fmt . Sprintf ( "%s:%s" , task . OriginChannel , task . OriginChatID ) ,
Content : announceContent ,
} )
}
2026-02-04 11:06:13 +00:00
}
func ( sm * SubagentManager ) GetTask ( taskID string ) ( * SubagentTask , bool ) {
sm . mu . RLock ( )
defer sm . mu . RUnlock ( )
task , ok := sm . tasks [ taskID ]
return task , ok
}
func ( sm * SubagentManager ) ListTasks ( ) [ ] * SubagentTask {
sm . mu . RLock ( )
defer sm . mu . RUnlock ( )
tasks := make ( [ ] * SubagentTask , 0 , len ( sm . tasks ) )
for _ , task := range sm . tasks {
tasks = append ( tasks , task )
}
return tasks
}
2026-02-12 12:14:21 +00:00
// SubagentTool executes a subagent task synchronously and returns the result.
// Unlike SpawnTool which runs tasks asynchronously, SubagentTool waits for completion
// and returns the result directly in the ToolResult.
type SubagentTool struct {
manager * SubagentManager
originChannel string
originChatID string
}
func NewSubagentTool ( manager * SubagentManager ) * SubagentTool {
return & SubagentTool {
manager : manager ,
originChannel : "cli" ,
originChatID : "direct" ,
}
}
func ( t * SubagentTool ) Name ( ) string {
return "subagent"
}
func ( t * SubagentTool ) Description ( ) string {
return "Execute a subagent task synchronously and return the result. Use this for delegating specific tasks to an independent agent instance. Returns execution summary to user and full details to LLM."
}
func ( t * SubagentTool ) Parameters ( ) map [ string ] interface { } {
return map [ string ] interface { } {
"type" : "object" ,
"properties" : map [ string ] interface { } {
"task" : map [ string ] interface { } {
"type" : "string" ,
"description" : "The task for subagent to complete" ,
} ,
"label" : map [ string ] interface { } {
"type" : "string" ,
"description" : "Optional short label for the task (for display)" ,
} ,
} ,
"required" : [ ] string { "task" } ,
}
}
func ( t * SubagentTool ) SetContext ( channel , chatID string ) {
t . originChannel = channel
t . originChatID = chatID
}
func ( t * SubagentTool ) Execute ( ctx context . Context , args map [ string ] interface { } ) * ToolResult {
task , ok := args [ "task" ] . ( string )
if ! ok {
return ErrorResult ( "task is required" ) . WithError ( fmt . Errorf ( "task parameter is required" ) )
}
label , _ := args [ "label" ] . ( string )
if t . manager == nil {
return ErrorResult ( "Subagent manager not configured" ) . WithError ( fmt . Errorf ( "manager is nil" ) )
}
// Execute subagent task synchronously via direct provider call
messages := [ ] providers . Message {
{
Role : "system" ,
Content : "You are a subagent. Complete the given task independently and provide a clear, concise result." ,
} ,
{
Role : "user" ,
Content : task ,
} ,
}
response , err := t . manager . provider . Chat ( ctx , messages , nil , t . manager . provider . GetDefaultModel ( ) , map [ string ] interface { } {
"max_tokens" : 4096 ,
} )
if err != nil {
return ErrorResult ( fmt . Sprintf ( "Subagent execution failed: %v" , err ) ) . WithError ( err )
}
// ForUser: Brief summary for user (truncated if too long)
userContent := response . Content
maxUserLen := 500
if len ( userContent ) > maxUserLen {
userContent = userContent [ : maxUserLen ] + "..."
}
// ForLLM: Full execution details
llmContent := fmt . Sprintf ( "Subagent task completed:\nLabel: %s\nResult: %s" ,
func ( ) string {
if label != "" {
return label
}
return "(unnamed)"
} ( ) ,
response . Content )
return & ToolResult {
ForLLM : llmContent ,
ForUser : userContent ,
Silent : false ,
IsError : false ,
Async : false ,
}
}