Tool

Give agents real capabilities: files, commands, and human approvals.

What it is

Tools are callable functions the LLM can request during an agent run. They are the primary way an agent interacts with the outside world: reading/writing files, executing commands, talking to services, or asking a human for confirmation.

Phero tools are just function tools from llm, registered on an agent.

Built-in tool packages

Phero includes a few ready-to-use tools under tool/. These are designed to be easy to attach to an agent. Exact behavior can be customized with llm.Tool.Use(...) middleware.

Tip: for anything with side effects (writes or shell commands), add an approval middleware.

Bash tool guardrails

The bash tool ships with several security options to restrict what the agent can run.

import (
    "time"

    "github.com/henomis/phero/tool/bash"
)

// Safe mode: applies a built-in blocklist of dangerous patterns
// and sets a 30-second timeout.
bashTool, err := bash.New(bash.WithSafeMode())

// Fine-grained control:
bashTool, err := bash.New(
    bash.WithWorkingDirectory("/tmp/sandbox"),
    bash.WithTimeout(10*time.Second),
    bash.WithBlocklist("curl", "wget"),       // blocked substrings
    bash.WithAllowlist("ls", "cat", "echo"),  // only these are allowed
)

File tool: prevent overwriting

By default CreateFileTool silently overwrites existing files. Pass WithNoOverwrite() to return ErrFileExists instead.

import "github.com/henomis/phero/tool/file"

createTool, err := file.NewCreateFileTool(
    file.WithWorkingDirectory("/workspace"),
    file.WithNoOverwrite(),
)
if err != nil {
    panic(err)
}

The examples/skills program combines skills with a file-writing tool that asks the user to approve writes before the underlying tool handler runs.

// From examples/skills (edited for brevity)

createFileTool, err := file.NewCreateFileTool()
if err != nil {
    panic(err)
}

tools = append(tools, createFileTool.Tool().Use(func(_ *llm.Tool, next llm.ToolHandler) llm.ToolHandler {
    return func(ctx context.Context, arguments string) (any, error) {
        var input *file.CreateFileInput
        if err := json.Unmarshal([]byte(arguments), &input); err != nil {
            return nil, &llm.ToolArgumentParseError{Err: err}
        }
        if err := writeValidationFunc(ctx, input); err != nil {
            return nil, err
        }
        return next(ctx, arguments)
    }
}))

for _, tool := range tools {
    if err := a.AddTool(tool); err != nil {
        panic(err)
    }
}
// From examples/skills (edited for brevity)

func writeValidationFunc(_ context.Context, input *file.WriteInput) error {
    fmt.Printf("Do you want to write to the file '%s'? (y/N): ", input.Path)
    var permission string
    _, scanErr := fmt.Scanln(&permission)
    if scanErr != nil {
        return fmt.Errorf("failed to read user input: %w", scanErr)
    }

    if strings.EqualFold(permission, "y") {
        return nil
    }

    return fmt.Errorf("user permission denied")
}

This pattern keeps the agent powerful while still giving the user control.

Default tools inside skills

When you convert a skill into a tool, Phero automatically gives the skill-agent a small local toolbox rooted at the skill directory: view, create_file, str_replace, and bash.

That makes skill execution self-contained: the skill can inspect files, make targeted edits, or run commands without needing separate tool wiring in the caller.

Run an example

These are good starting points to see tools in action (provider setup is described in each example README):

# Skills + built-in file/bash tools (run from example dir)
cd ./examples/skills
go run .

    # Multi-agent workflow (uses a custom restricted command tool)
cd -
go run ./examples/multi-agent-workflow

Related packages