Tools

Tools are how the agent reads files, runs commands, sends generated files, and calls trusted app code.

Default tools

defaultTools() exposes heypi's built-in runtime tools. By default, it returns:

bash, read, write, edit, grep, find, ls, attach, history

These run through the selected runtime when the runtime implements the operation. attach marks a generated runtime file for upload with the final chat reply.

Configure default tools with the agent builtinTools option:

import { approval, defaultTools, loadAgent } from "@hunvreus/heypi";

loadAgent("./agent", {
  model: "openai/gpt-5.4-mini",
  builtinTools: defaultTools({
    bash: { confirm: approval.command() },
    write: false,
    edit: false,
  }),
});

bash uses command confirmation by default. Use bash: true only for trusted agents where commands should run without approval.

Options

Option Default Description
bash { confirm: approval.command() } Run shell commands through the selected runtime. Pass true to remove default confirmation, false to disable, or { confirm } to customize approval.
read true Read runtime files.
write true Write runtime files.
edit true Edit runtime files.
grep true Search runtime file contents.
find true Find runtime files by path/name.
ls true List runtime directories.
attach true Attach a generated runtime file to the final chat reply.
history true Search current-thread message history.

Each option accepts false, true, or { confirm }. Only bash has a confirmation policy by default.

The public config type is DefaultToolsConfig.

Custom tools

Custom tools run as trusted JavaScript in the Node app process. Prefer authoring them as default exports under agent/tools/:

import { defineTool } from "@hunvreus/heypi/authoring";
import { z } from "zod";

export default defineTool({
  description: "List files in the active runtime workspace.",
  input: z.object({}),
  run: async (_params, ctx) => {
    const result = await ctx.runtime.bash?.({ command: "find . -maxdepth 2 -type f", signal: ctx.signal });
    return result?.out ?? "runtime does not support bash";
  },
});

When this file is loaded by loadAgent("./agent"), the filename becomes the tool name: inspect_workspace. Tools passed directly in agent.tools must set name.

Passing tools overrides agent/tools/ discovery for that category. Use tools: [...loadTools("./agent/tools"), myTool] only when you intentionally want convention-loaded tools plus inline tools.

Do not put defaultTools() in tools. Built-in runtime tools belong in builtinTools; heypi rejects legacy tools: defaultTools() config.

Use @hunvreus/heypi/authoring inside discovered agent/ modules. App entrypoints such as index.ts should keep importing runtime config helpers from @hunvreus/heypi.

Use ctx.runtime when a custom tool wants command or file work to follow the selected runtime.

Options

Option Required Description
name Only for direct config Tool name exposed to the model. May be omitted for default-exported tools loaded from agent/tools/; the file stem becomes the name.
description Yes Short model-facing description of when to use the tool.
input Yes Tool input schema. Zod, TypeBox, and raw JSON Schema are supported.
run Yes Trusted JavaScript handler. Receives input and { runtime, runtimeScope, signal }.
label No Human label for approvals and logs. Defaults to name.
confirm No Confirmation policy for this tool. See Confirmation.

For Zod schemas, heypi parses input before confirm and run; invalid input fails the call. TypeBox and raw JSON Schema inputs are exposed to the model but are not runtime validators by themselves.

Trust boundary

Custom tools and Pi extensions run as trusted JavaScript in the Node app process. They have the same filesystem, network, env var, database, and SDK access as the app itself.

Use ctx.runtime when shell or file work should go through the selected runtime. The JavaScript tool body itself is not sandboxed.

Confirmation

confirm controls whether a tool call needs approval:

const pageService = defineTool({
  name: "page_service",
  description: "Record a service page request.",
  input: z.object({
    service: z.string(),
    reason: z.string(),
  }),
  confirm: ({ service }) => ({ message: `Page ${service}.` }),
  run: async ({ service, reason }) => `page recorded: service=${service} reason=${reason}`,
});

Return values:

Return value Effect
undefined or false Allow the call immediately.
{ message } or { reason } Ask for approval before running.
{ block } Block the call without asking for approval.
{ details } Add fields to the approval card. Use with message or reason.
{ policyReason } Internal/audit reason for why this policy matched.

For approval cards, use message plus optional details:

confirm: ({ command }) => ({
  message: "Run deployment command.",
  details: [{ label: "Command", value: command, format: "code" }],
})

approval.command() classifies bash commands. It blocks destructive commands, asks for approval for risky commands, and allows low-risk commands.

defaultTools({
  bash: {
    confirm: approval.command({
      allow: [/^curl -I https:\/\/status\.example\.com\b/],
      approve: [/\bmake deploy\b/],
      block: [/\bgh repo delete\b/],
    }),
  },
});

The classifier is a guardrail, not a sandbox. Use just-bash, Docker, Gondolin, or another runtime provider for isolation.

Managed tools

Some top-level feature config adds tools automatically. These tools are not returned by defaultTools() and do not need to be listed manually in agent.tools.

Feature config Added tools What they do
memory memory_read, memory_write, memory_replace, memory_delete Let the agent read and update scoped durable memory.
skills skill_list, skill_read, skill_write, skill_patch, skill_delete Let the agent manage scoped runbooks/procedures stored by heypi.
secrets secret_request Let the agent ask the user for encrypted secrets and receive scoped file paths, not raw secret values in chat.

Managed tools follow their feature's policy. For example, memory and skills writes are controlled by their writePolicy; secret requests require secrets.enabled.