Custom Tools

Built-in Tools

Pass sdk.DefaultTools() in sdk.Config.Tools to get the full set of built-in tools:

ToolDescription
readRead file contents with offset/limit support
writeCreate or overwrite files
editSearch-and-replace edits within files
bashExecute shell commands
grepSearch file contents via regex
lsList directory contents
findLocate files using glob patterns

bash, write, and edit are destructive. In --dry-run mode they preview what they would do without executing.


Tool Interface

Implement sdk.Tool to create a custom tool:

type Tool interface {
    Name() string
    Description() string
    Schema() json.RawMessage       // JSON Schema for the input parameters
    Execute(ctx context.Context, args json.RawMessage, update ToolUpdate) (*ToolResult, error)
    IsReadOnly() bool              // if true, tool is allowed in dry-run mode
}

ToolUpdate is a callback for streaming partial output while the tool runs:

type ToolUpdate func(content string)

Example: Custom Tool

type CountLinesTool struct{}

func (t *CountLinesTool) Name() string { return "count_lines" }
func (t *CountLinesTool) Description() string {
    return "Count the number of lines in a file"
}
func (t *CountLinesTool) Schema() json.RawMessage {
    return json.RawMessage(`{
        "type": "object",
        "properties": {
            "path": {"type": "string", "description": "File path to count lines in"}
        },
        "required": ["path"]
    }`)
}
func (t *CountLinesTool) IsReadOnly() bool { return true }

func (t *CountLinesTool) Execute(ctx context.Context, args json.RawMessage, update sdk.ToolUpdate) (*sdk.ToolResult, error) {
    var input struct {
        Path string `json:"path"`
    }
    if err := json.Unmarshal(args, &input); err != nil {
        return nil, err
    }
    data, err := os.ReadFile(input.Path)
    if err != nil {
        return &sdk.ToolResult{Content: err.Error(), IsError: true}, nil
    }
    n := strings.Count(string(data), "\n") + 1
    return &sdk.ToolResult{Content: fmt.Sprintf("%d lines", n)}, nil
}

Register alongside the built-in tools:

ag, _ := sdk.NewAgent(sdk.Config{
    Provider: "ollama",
    Model:    "llama3.2",
    Tools:    append(sdk.DefaultTools(), &CountLinesTool{}),
})

Selective Tools

Pass only the tools you want rather than the full default set:

tools := sdk.ToolsFor("read", "grep", "ls")   // subset by name

Or build the list manually to include only read-only tools for a sandboxed agent.