Tool

Give agents real capabilities: files, commands, sub-agents, 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, delegating to sub-agents, or asking a human for confirmation.

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

Built-in tool packages

Phero includes ready-to-use tools under tool/. Each tool is constructed with a New… function and exposes a .Tool() method that returns the *llm.Tool to pass to AddTool. Exact behavior can be customized with llm.Tool.Use(...) middleware.

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

Agent tool (tool/agent)

The agent tool lets an orchestrator delegate an open-ended task to a purpose-built sub-agent at runtime. The LLM provides the sub-agent's name, description (system prompt), and input (task instructions). The tool creates an agent, runs it, and returns the text result.

import agenttool "github.com/henomis/phero/tool/agent"

// Pass the same LLM client used by the orchestrator.
agentTool, err := agenttool.New(llmClient)
if err != nil {
    panic(err)
}

if err := a.AddTool(agentTool.Tool()); err != nil {
    panic(err)
}

Optionally pass tools that every created sub-agent will have access to:

readTool, _  := file.NewReadTool()
writeTool, _ := file.NewWriteTool()

agentTool, err := agenttool.New(llmClient, readTool.Tool(), writeTool.Tool())
if err != nil {
    panic(err)
}

The JSON input schema expected from the LLM:

Errors: ErrLLMRequired, ErrNilInput, ErrNameRequired, ErrDescriptionRequired, ErrInputRequired.

Filesystem tools (tool/file)

Each filesystem operation is a separate constructor returning its own typed struct. All constructors accept shared Option values.

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

readTool, err  := file.NewReadTool(file.WithWorkingDirectory("/workspace"))
writeTool, err := file.NewWriteTool(file.WithWorkingDirectory("/workspace"))
editTool, err  := file.NewEditTool(file.WithWorkingDirectory("/workspace"))
globTool, err  := file.NewGlobTool(file.WithWorkingDirectory("/workspace"))
grepTool, err  := file.NewGrepTool(file.WithWorkingDirectory("/workspace"))

// Each exposes .Tool() to get the *llm.Tool:
if err := a.AddTool(readTool.Tool()); err != nil {
    panic(err)
}

Available options:

Using middleware to require user confirmation before a write:

writeTool, err := file.NewWriteTool(file.WithWorkingDirectory("/workspace"))
if err != nil {
    panic(err)
}

writeTool.Tool().Use(func(_ *llm.Tool, next llm.ToolHandler) llm.ToolHandler {
    return func(ctx context.Context, arguments string) (any, error) {
        var input file.WriteInput
        if err := json.Unmarshal([]byte(arguments), &input); err != nil {
            return nil, err
        }
        fmt.Printf("Write to %s? (y/N): ", input.FilePath)
        var answer string
        fmt.Scanln(&answer)
        if !strings.EqualFold(answer, "y") {
            return nil, fmt.Errorf("write denied by user")
        }
        return next(ctx, arguments)
    }
})

if err := a.AddTool(writeTool.Tool()); err != nil {
    panic(err)
}

Bash tool (tool/bash)

The bash tool runs shell commands with configurable guardrails and supports background execution. bash.New returns a *Tool that exposes three *llm.Tool values:

import (
    "time"

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

bashTool, err := bash.New(
    bash.WithSafeMode(),                         // built-in dangerous-command blocklist + 30 s timeout
    bash.WithWorkingDirectory("/tmp/sandbox"),
)
if err != nil {
    panic(err)
}

// Register all three tools so the agent can manage background processes:
if err := a.AddTool(bashTool.Tool());       err != nil { panic(err) }
if err := a.AddTool(bashTool.OutputTool()); err != nil { panic(err) }
if err := a.AddTool(bashTool.KillTool());   err != nil { panic(err) }

Guardrail options:

Background execution fields in Input:

Human tool (tool/human)

The human tool pauses the agent to collect structured input from the user. You provide the interactor — the function that presents questions and reads answers — via WithInteractor. This keeps the tool backend-agnostic (terminal, web UI, etc.).

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

humanTool, err := human.New(
    human.WithInteractor(consoleInteractor),
)
if err != nil {
    panic(err)
}

if err := a.AddTool(humanTool.Tool()); err != nil {
    panic(err)
}

The Interactor signature:

type Interactor func(ctx context.Context, input *human.Input) (map[string]human.Answer, error)

The LLM supplies an Input with 1–4 questions. Each Question has:

A minimal terminal interactor:

func consoleInteractor(_ context.Context, in *human.Input) (map[string]human.Answer, error) {
    answers := make(map[string]human.Answer)
    for _, q := range in.Questions {
        fmt.Printf("[%s] %s\n", q.Header, q.Question)
        for i, opt := range q.Options {
            fmt.Printf("  %d) %s — %s\n", i+1, opt.Label, opt.Description)
        }
        fmt.Print("Your choice: ")
        var line string
        fmt.Scanln(&line)
        answers[q.Header] = human.Answer{Selections: []string{strings.TrimSpace(line)}}
    }
    return answers, nil
}

See examples/human-in-the-loop for a complete working example with a multi-step approval flow.

Skill dispatcher (tool/skill)

Use the skill dispatcher to expose a single skill tool that loads SKILL.md instructions and a base path into the current conversation, keeping the agent toolset compact.

import skilltool "github.com/henomis/phero/tool/skill"

dispatcher, err := skilltool.New("./skills")
if err != nil {
    panic(err)
}

if err := a.AddTool(dispatcher.Tool()); err != nil {
    panic(err)
}

The dispatcher scans skillsRootPath for subdirectories containing SKILL.md files, builds a catalog, and embeds it in the tool description so the model can select the right skill by name at runtime.

Available options:

Run an example

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

# Human-in-the-loop: multi-step approval flow
go run ./examples/human-in-the-loop

# Skills + built-in file/bash tools
cd ./examples/skills && go run .

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

Related packages