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.
- Agent tool (
tool/agent): create and run a sub-agent at runtime with a given name, role, and instructions - Filesystem tools (
tool/file): five separate tools —read,write,edit,glob,grep - Bash tool (
tool/bash): run bash commands with optional guardrails (blocklist, allowlist, timeout, safe mode) and background execution - Human tool (
tool/human): present structured questions and collect user answers mid-run - Skill dispatcher (
tool/skill): load a namedSKILL.mdinto the current conversation
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:
name: short name for the delegated agentdescription: system prompt / role descriptioninput: the task or question the agent should solve
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:
WithWorkingDirectory(dir): confine all operations to a directory; paths are validated against it to prevent traversalWithMaxFileSize(bytes): limit the file size the read tool will return (0 = unlimited)WithNoOverwrite(): configure write to returnErrFileExistswhen the target already exists
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:
Tool(): the mainbashtool — execute a command, optionally in the backgroundOutputTool():bash_output— retrieve incremental output from a running background shellKillTool():kill_shell— terminate a background shell by ID
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:
WithSafeMode(): enables a built-in blocklist (rm -rf /,dd if=, fork bombs, …) and a 30 s timeoutWithTimeout(d): fixed maximum execution time per commandWithDefaultTimeout(d): timeout used when the LLM does not supply one in the request (default: 120 s)WithMaxTimeout(d): cap on the LLM-supplied per-request timeout value (default: 600 s)WithMaxOutputChars(n): output truncation threshold (default: 30 000 chars)WithBlocklist(patterns...): any command containing a pattern (case-insensitive) returnsErrCommandBlockedWithAllowlist(patterns...): when set, the command must match at least one pattern orErrCommandNotAllowedis returnedWithWorkingDirectory(dir): directory in which commands run
Background execution fields in Input:
RunInBackground bool: start the command in the background; the tool returns abash_idimmediatelyTimeout int: optional per-request timeout in milliseconds, capped byWithMaxTimeout
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:
Header string: short label (max 12 characters); used as the answer map keyQuestion string: text shown to the user (must end with?)MultiSelect bool: whether multiple options can be selectedOptions []Choice: 2–4 options, each with aLabel(1–5 words) andDescription
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:
WithAvailableSkillsLocation(loc): override the location string shown in the skill catalogWithParser(parser): inject a customParserimplementation (useful for testing)
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