Documentation Index Fetch the complete documentation index at: https://mintlify.com/sipeed/picoclaw/llms.txt
Use this file to discover all available pages before exploring further.
Overview
PicoClaw’s tool system is extensible - you can create custom tools to give your agent new capabilities. Tools are Go functions that follow a specific interface and get registered with the ToolRegistry.
Every tool must implement the Tool interface:
type Tool interface {
Name () string
Description () string
Parameters () map [ string ] interface {}
Execute ( params map [ string ] interface {}) ( * ToolResult , error )
}
package tools
import (
" fmt "
" time "
)
type TimezoneTool struct {}
func NewTimezoneTool () * TimezoneTool {
return & TimezoneTool {}
}
Step 2: Implement the Interface
func ( t * TimezoneTool ) Name () string {
return "get_timezone_time"
}
func ( t * TimezoneTool ) Description () string {
return "Get current time in a specific timezone"
}
func ( t * TimezoneTool ) Parameters () map [ string ] interface {} {
return map [ string ] interface {}{
"type" : "object" ,
"properties" : map [ string ] interface {}{
"timezone" : map [ string ] interface {}{
"type" : "string" ,
"description" : "Timezone name (e.g., America/New_York, Asia/Tokyo)" ,
},
},
"required" : [] string { "timezone" },
}
}
func ( t * TimezoneTool ) Execute ( params map [ string ] interface {}) ( * ToolResult , error ) {
timezone , ok := params [ "timezone" ].( string )
if ! ok {
return nil , fmt . Errorf ( "timezone parameter required" )
}
loc , err := time . LoadLocation ( timezone )
if err != nil {
return NewToolResultError ( fmt . Errorf ( "invalid timezone: %w " , err ))
}
now := time . Now (). In ( loc )
result := fmt . Sprintf ( "Current time in %s : %s " , timezone , now . Format ( time . RFC1123 ))
return NewToolResultSuccess ( result ), nil
}
In your agent initialization code:
package agent
import (
" github.com/sipeed/picoclaw/pkg/tools "
)
func NewAgentInstance ( ... ) * AgentInstance {
// ... existing setup ...
toolsRegistry := tools . NewToolRegistry ()
// Register built-in tools
toolsRegistry . Register ( tools . NewReadFileTool ( ... ))
toolsRegistry . Register ( tools . NewWriteFileTool ( ... ))
// ... other tools ...
// Register your custom tool
toolsRegistry . Register ( tools . NewTimezoneTool ())
// ... rest of setup ...
}
Tools return *ToolResult with different types:
// Success result
return NewToolResultSuccess ( "Operation completed successfully" ), nil
// Error result
return NewToolResultError ( fmt . Errorf ( "something went wrong" )), nil
// Custom result
return & ToolResult {
Type : "success" ,
Content : "Custom content" ,
}, nil
Here’s a more complex example - a tool that calls an external API:
package tools
import (
" encoding/json "
" fmt "
" io "
" net/http "
" time "
)
type WeatherTool struct {
apiKey string
client * http . Client
}
func NewWeatherTool ( apiKey string ) * WeatherTool {
return & WeatherTool {
apiKey : apiKey ,
client : & http . Client {
Timeout : 10 * time . Second ,
},
}
}
func ( w * WeatherTool ) Name () string {
return "get_weather"
}
func ( w * WeatherTool ) Description () string {
return "Get current weather for a city"
}
func ( w * WeatherTool ) Parameters () map [ string ] interface {} {
return map [ string ] interface {}{
"type" : "object" ,
"properties" : map [ string ] interface {}{
"city" : map [ string ] interface {}{
"type" : "string" ,
"description" : "City name" ,
},
"units" : map [ string ] interface {}{
"type" : "string" ,
"description" : "Temperature units (metric or imperial)" ,
"enum" : [] string { "metric" , "imperial" },
"default" : "metric" ,
},
},
"required" : [] string { "city" },
}
}
func ( w * WeatherTool ) Execute ( params map [ string ] interface {}) ( * ToolResult , error ) {
city , ok := params [ "city" ].( string )
if ! ok {
return NewToolResultError ( fmt . Errorf ( "city parameter required" ))
}
units := "metric"
if u , ok := params [ "units" ].( string ); ok {
units = u
}
url := fmt . Sprintf ( "https://api.openweathermap.org/data/2.5/weather?q= %s &units= %s &appid= %s " ,
city , units , w . apiKey )
resp , err := w . client . Get ( url )
if err != nil {
return NewToolResultError ( fmt . Errorf ( "API request failed: %w " , err ))
}
defer resp . Body . Close ()
if resp . StatusCode != http . StatusOK {
return NewToolResultError ( fmt . Errorf ( "API returned status %d " , resp . StatusCode ))
}
body , err := io . ReadAll ( resp . Body )
if err != nil {
return NewToolResultError ( fmt . Errorf ( "failed to read response: %w " , err ))
}
var weather map [ string ] interface {}
if err := json . Unmarshal ( body , & weather ); err != nil {
return NewToolResultError ( fmt . Errorf ( "failed to parse response: %w " , err ))
}
// Format response
result := fmt . Sprintf ( "Weather in %s : %v ° %s , %s " ,
city ,
weather [ "main" ].( map [ string ] interface {})[ "temp" ],
map [ string ] string { "metric" : "C" , "imperial" : "F" }[ units ],
weather [ "weather" ].([] interface {})[ 0 ].( map [ string ] interface {})[ "description" ],
)
return NewToolResultSuccess ( result ), nil
}
Register with API key:
toolsRegistry . Register ( tools . NewWeatherTool ( cfg . Tools . Weather . APIKey ))
Workspace Restriction
If your tool accesses files, respect workspace restrictions:
func ( t * CustomFileTool ) Execute ( params map [ string ] interface {}) ( * ToolResult , error ) {
path := params [ "path" ].( string )
// Validate path is within workspace
if t . restrictToWorkspace {
if ! isWithinWorkspace ( path , t . workspace ) {
return NewToolResultError ( fmt . Errorf ( "path outside workspace" ))
}
}
// ... proceed with operation ...
}
Always validate input parameters:
func ( t * Tool ) Execute ( params map [ string ] interface {}) ( * ToolResult , error ) {
// Type check
value , ok := params [ "param" ].( string )
if ! ok {
return NewToolResultError ( fmt . Errorf ( "param must be a string" ))
}
// Range check
if len ( value ) > 1000 {
return NewToolResultError ( fmt . Errorf ( "param too long (max 1000 chars)" ))
}
// Pattern validation
if ! validPattern . MatchString ( value ) {
return NewToolResultError ( fmt . Errorf ( "param contains invalid characters" ))
}
// ... proceed ...
}
Timeout Protection
For long-running operations, use contexts:
import " context "
func ( t * Tool ) Execute ( params map [ string ] interface {}) ( * ToolResult , error ) {
ctx , cancel := context . WithTimeout ( context . Background (), 30 * time . Second )
defer cancel ()
result , err := t . doLongOperation ( ctx , params )
if err != nil {
return NewToolResultError ( err )
}
return NewToolResultSuccess ( result ), nil
}
Configuration
Add tool-specific configuration:
{
"tools" : {
"weather" : {
"enabled" : true ,
"api_key" : "your-api-key" ,
"default_units" : "metric"
}
}
}
Access in your tool:
func NewWeatherTool ( cfg * config . WeatherConfig ) * WeatherTool {
return & WeatherTool {
apiKey : cfg . APIKey ,
defaultUnits : cfg . DefaultUnits ,
}
}
Best Practices
Clear descriptions Write detailed tool descriptions so the LLM knows when to use your tool
Validate inputs Always validate and sanitize input parameters
Handle errors gracefully Return meaningful error messages that help the agent understand what went wrong
Use timeouts Protect against hanging operations with context timeouts
Respect security Follow workspace restrictions and safety patterns
Keep it focused One tool, one clear purpose - don’t create Swiss Army knives
Create unit tests for your custom tool:
package tools
import (
" testing "
)
func TestTimezoneTool ( t * testing . T ) {
tool := NewTimezoneTool ()
// Test valid timezone
result , err := tool . Execute ( map [ string ] interface {}{
"timezone" : "America/New_York" ,
})
if err != nil {
t . Fatalf ( "unexpected error: %v " , err )
}
if result . Type != "success" {
t . Errorf ( "expected success, got %s " , result . Type )
}
// Test invalid timezone
result , err = tool . Execute ( map [ string ] interface {}{
"timezone" : "Invalid/Timezone" ,
})
if result . Type != "error" {
t . Errorf ( "expected error for invalid timezone" )
}
}
Next Steps
Tool Registry Learn about the tool registry and how tools are discovered
Built-in Tools Study the built-in tools for examples
Agent Configuration Configure tool access and restrictions
MCP Tools Alternative: Use Model Context Protocol for external tools