Skip to content

Tools

Which tools the agent may use — the built-in toolset, the dev-command and general-purpose shell tools, and your own custom shell commands — is configured here. This page is part of the configuration reference; for the interactive approval flow behind the shell tool see the shell tool and approvals guide, and for writing your own tools see the custom tools guide.

Say you want gth code to run your test suite when it needs to, and to have a ready-made deploy_staging command it can call. Two knobs cover both: builtInTools (the built-in and dev-command tools) and customTools (your own shell commands).

.gsloth.config.json:

{
"llm": { "type": "anthropic", "model": "claude-sonnet-4-5" },
"customTools": {
"deploy_staging": {
"command": "npm run deploy:staging",
"description": "Deploy the application to staging environment"
}
},
"commands": {
"code": {
"filesystem": "all",
"builtInTools": {
"gth_checklist": true,
"run_tests": { "command": "npm test" },
"run_shell_command": { "timeout": 300000 }
}
}
}
}

Then start a coding session:

Terminal window
gth code

The agent now has run_tests (runs npm test), the general-purpose run_shell_command (arbitrary commands, each behind a human-approval prompt — on by default in code mode, tuned here with a longer timeout), and the deploy_staging custom tool. Note the explicit "gth_checklist": true: because a builtInTools object replaces the default set rather than extending it, any default you still want must be re-listed.

The rest of this page is the reference for those knobs.

builtInTools selects and configures which built-in tools the agent loads. It can be set at the top level or per command (commands.<command>.builtInTools); a per-command value replaces the top-level one. Available tools:

Tool Description
gth_checklist Planning / todo checklist for multi-step work. Renders as a live checkbox panel in the TUI. Enabled by default.
gth_grep Regex search over file contents (ripgrep-backed, with an in-process fallback) — finds where a symbol or string appears, complementing search_files, which matches file names. Available in every mode; needs no shell approval. Enabled by default. Honors .aiignore and takes a fileSet option — see Content search (gth_grep) below.
gth_web_fetch Fetch content from an HTTP/HTTPS URL.
gth_gh_read_file Read a whole file from the pull request under review, through the GitHub API. Loaded by gth pr, and by any gth review whose content source is github — from config or from --content-source github; needs an authenticated gh. Enabled by default on those runs. Takes enabled and maxBytes options — see GitHub file reads during a PR review below.
gth_status_update Print a short status line to the console.
show_a2ui_surface (AG-UI) render an A2UI surface in the web client.
run_tests / run_lint / run_build / run_single_test Dev-command tools — run the configured shell command. Only active in code / exec (and ask --write). See Development Tools.
run_shell_command Opt-in general-purpose shell tool (arbitrary commands, human-approved). On by default in code mode. See Shell tool.

builtInTools accepts two shapes:

  • a string array — each named tool is enabled: ["gth_checklist", "gth_web_fetch"];
  • an object registry keyed by tool name, whose values enable (true), force-disable (false), or configure (an object) each tool.

The default is ["gth_checklist", "gth_grep"]. Setting your own builtInTools replaces this set entirely, so re-list any default you want to keep (e.g. "gth_checklist": true, "gth_grep": true). Example — add web fetch while keeping the checklist:

{
"builtInTools": ["gth_checklist", "gth_web_fetch"]
}

The object form also carries the dev/shell tool configuration (in 1.x this lived in a separate per-command devTools key, now removed — see Migration). Example — keep the checklist, add web fetch, configure the test/build commands, and tune the shell:

{
"builtInTools": {
"gth_checklist": true,
"gth_web_fetch": true,
"run_tests": { "command": "npm test" },
"run_build": { "command": "npm run build" },
"run_shell_command": { "timeout": 300000, "judge": { "enabled": true } }
}
}

Turn the (code-mode default-on) shell OFF with { "run_shell_command": false }.

Note: because the object form (like the array form) replaces the default set, disabling one tool (e.g. { "run_shell_command": false }) also drops the defaults (gth_checklist, gth_grep) unless you list them too. To keep them, add "gth_checklist": true / "gth_grep": true to the registry.

gth_grep is enabled by default. Its fileSet option, set through the builtInTools registry, chooses which files it searches:

fileSet Corpus searched
gitignore (default) Respects .gitignore / .ignore and skips hidden dot-files (ripgrep’s native behavior).
all Everything except the noise directories .git, node_modules, dist, .idea.
{
"builtInTools": {
"gth_checklist": true,
"gth_grep": { "fileSet": "all" }
}
}

Disable it with { "gth_grep": { "enabled": false } } (or { "gth_grep": false }).

Whatever the fileSet, gth_grep honors .aiignore. It reads file contents through its own search path, so it enforces the same .aiignore privacy boundary as the filesystem tools: a file hidden by .aiignore is never searched or returned, even under fileSet: "all", and neither is any file inside a directory that .aiignore hides. (.gitignore decides what stays out of version control; .aiignore decides what the AI may read at all — so a tracked, non-ignored file can still be kept out of gth_grep by .aiignore.)

GitHub file reads during a PR review (gth_gh_read_file)

Section titled “GitHub file reads during a PR review (gth_gh_read_file)”

A large pull request arrives as a truncated diff, which leaves the reviewer judging a hunk whose surrounding file it cannot see. gth_gh_read_file closes that gap: the review agent asks for one repository-relative path and gets the whole file back, fetched from the pull request’s own head repository and ref through the GitHub API using the authenticated gh CLI that gth pr already requires. It never touches your working copy — which is what makes it safe in a pull_request_target job, where the untrusted head is deliberately not checked out — and when gh is missing or unauthenticated it returns an explanation instead of failing the review.

It is enabled by default on gth pr, whose content source is github, and on any gth review whose content source is github — set by commands.review.contentSource, by the root contentSource, or by --content-source github on the run itself. The flag outranks both config layers in both directions, so gth review --content-source git in a project whose config selects github loads no GitHub tool: that run reviews a local diff with no pull request behind it. No other command loads it.

Say your CI review must not call the GitHub contents API at all. Turn it off for gth pr:

{
"commands": {
"pr": {
"builtInTools": {
"gth_gh_read_file": false,
"gth_checklist": true,
"gth_grep": true
}
}
}
}

{ "gth_gh_read_file": { "enabled": false } } disables it the same way, and is the form to use when the entry also carries maxBytes.

gth_checklist and gth_grep are re-listed because a builtInTools object replaces the set it would otherwise inherit rather than extending it. Writing only the one key you meant to change is the easy mistake here: it silently drops both defaults from your PR reviews. The same rule decides what a per-command registry inherits — nothing. A root builtInTools entry for a tool that commands.pr.builtInTools does not name does not carry over into pr; that tool falls back to its default, which for this one means enabled.

To keep the tool but bound how much of the context window a single call can consume, set maxBytes — the ceiling on the decoded file text, 614400 (600 KiB) by default. It is deliberately generous, because the tool exists for exactly the case where the diff truncated. A file over the ceiling comes back cut at it and carries a marker naming the tool and the cap, so the model knows it is reading an incomplete file rather than reasoning about one it only half received:

{
"builtInTools": {
"gth_gh_read_file": { "maxBytes": 200000 },
"gth_checklist": true,
"gth_grep": true
}
}

Set at the root like this it applies to both review and pr. An out-of-range or non-numeric maxBytes falls back to the default.

The code / exec commands (and ask --write) can run development tools, configured under the unified builtInTools registry (in 1.x this was a separate per-command devTools key, now removed — see Migration).

The dev-command tools are defined in packages/agent/src/tools/GthDevToolkit.ts; each is configured with a { "command": "…" } object:

  • run_tests: Executes the full test suite.
  • run_single_test: Runs a single test file. The test path must be relative.
  • run_lint: Runs the linter, potentially with auto-fix.
  • run_build: Builds the project.

These tools execute the configured shell commands and capture their output.

Note: a per-command builtInTools object (like the root one) replaces the root set entirely, including the default gth_checklist planning tool. List "gth_checklist": true explicitly in the command’s registry to keep it (as the examples below do).

Example configuration including dev tools (from .gsloth.config.json):

{
"llm": {
"type": "xai",
"model": "grok-4-0709"
},
"commands": {
"code": {
"filesystem": "all",
"builtInTools": {
"gth_checklist": true,
"run_build": { "command": "npm build" },
"run_tests": { "command": "npm test" },
"run_lint": { "command": "npm run lint-n-fix" },
"run_single_test": { "command": "npm test" }
}
}
}
}

Note: For run_single_test, the command can include a placeholder like ${testPath} for the test file path. Security validations are in place to prevent path traversal or injection.

General-purpose shell tool (run_shell_command)

Section titled “General-purpose shell tool (run_shell_command)”

run_shell_command lets the agent run arbitrary shell commands it composes itself. It is ON by default in code mode (each invocation still goes through the approvals gate), and OFF in exec / ask --write unless enabled. Its builtInTools entry carries execution settings only:

  • true / false — enable / force-disable (an object without enabled also defaults ON in code).
  • timeout — per-command wall-clock limit in milliseconds (default 120000).
  • maxOutputBytes — byte budget for the captured output returned to the model (default 100000).

Who may run a command is configured separately, in the top-level approvals setting — not here.

{
"commands": {
"code": {
"filesystem": "all",
"builtInTools": {
"gth_checklist": true,
"run_shell_command": {
"timeout": 300000,
"maxOutputBytes": 200000
}
}
}
}
}

Approvals are a top-level key (settable per command as commands.<cmd>.approvals, which overrides only the fields it names: mode, rater, alignmentChecker, raterTimeoutMs and allow replace the root’s, while deny and escalate add to it rather than replacing it — a command may narrow what runs unprompted, never widen what is prohibited). It takes one of five mode names — manual, write, assisted (the default), auto, bypass — either on its own or as mode inside an object carrying the extras. See Migration for the retired keys.

  • mode — the approvals mode. Defaults to assisted in every context, interactive or not. What changes without a human is what an escalation does (it exits non-zero rather than prompting), not which mode the session starts on.
  • rater — the name of an identity profile whose model rates, instead of the session model. A name that does not resolve is a config error. Only consulted at assisted and auto.
  • alignmentChecker — the name of an identity profile for the second check at Auto, which asks whether a command the rater declined is what you asked for. Defaults to whatever rater resolves to, so you write this one only to have the two questions asked by different models. A name that does not resolve is a config error. Only consulted at auto, and it has no timeout of its own — raterTimeoutMs covers it.
  • allow — what you trust. Checked before the rater at every mode except bypass.
  • deny — what never runs. A shell entry is checked before allow and before the rater, and it still applies under bypass. An entry naming a tool is compared only where the mode gates that tool, so not at assisted, auto or bypass — see Which entries a mode consults.
  • escalate — what asks you rather than running, whatever the mode would otherwise have done. A shell entry asks at every mode except bypass, where only deny is still consulted. An entry naming a tool is compared only where the mode gates that tool, so not at assisted, auto or bypass — see Which entries a mode consults.

Entries in all three are explicit objects (type, matcher and pattern are always required); where more than one list matches, the most restrictive wins. The three are read-only input: they are merged with what you approve or reject at the prompt, and never written back to your config.

{ "approvals": "assisted" }
{
"approvals": {
"mode": "auto",
"rater": "safety-rater",
"deny": [
{ "type": "shell", "matcher": "glob", "pattern": "npm publish*" },
{ "type": "shell", "matcher": "glob", "pattern": "git push --force*" }
]
}
}

Full walkthrough: Shell tool & approvals.

Custom tools allow you to define custom shell commands that the AI can execute across all commands or specific commands. Unlike development tools (which are predefined and code-specific), custom tools are fully user-defined and can be used for any purpose: deployment, migration, testing, automation, or any other shell command you need. For a worked, realistic example see the custom tools guide.

  • Available Globally: Custom tools work in ALL commands (pr, review, code, ask, chat) by default
  • Per-Command Control: Each command can override or disable custom tools
  • Parameter Support: Commands can accept dynamic parameters with security validation
  • Security: Built-in validation prevents shell injection, directory traversal, and other attacks

Define custom tools at the root level to make them available across all commands:

{
"llm": {
"type": "vertexai",
"model": "gemini-2.5-pro"
},
"customTools": {
"deploy_staging": {
"command": "npm run deploy:staging",
"description": "Deploy the application to staging environment"
},
"run_e2e_tests": {
"command": "npm run test:e2e",
"description": "Run end-to-end tests"
}
}
}

Custom tools can accept parameters that are validated for security:

{
"customTools": {
"run_migration": {
"command": "npm run migrate -- ${migrationName}",
"description": "Run a specific database migration",
"parameters": {
"migrationName": {
"description": "Name of the migration to run"
}
}
},
"docker_build": {
"command": "docker build -t ${imageName}:${tag} .",
"description": "Build Docker image with specified name and tag",
"parameters": {
"imageName": {
"description": "Name of the Docker image"
},
"tag": {
"description": "Tag for the Docker image"
}
}
}
}
}

Custom tools can have an optional timeout (in seconds). If the command exceeds this duration it is killed:

{
"customTools": {
"deploy_staging": {
"command": "npm run deploy:staging",
"description": "Deploy to staging environment",
"timeout": 120
}
}
}

When omitted, no timeout is applied.

Parameter Interpolation:

  • Use ${parameterName} placeholders in commands
  • If no placeholders exist, parameters are appended in definition order
  • All parameters are validated to prevent security issues

You can override or disable custom tools for specific commands:

Override for specific command:

{
"customTools": {
"deploy": {
"command": "npm run deploy:prod",
"description": "Deploy to production"
}
},
"commands": {
"pr": {
"customTools": {
"deploy": {
"command": "npm run deploy:staging",
"description": "Deploy to staging for PR review"
}
}
}
}
}

Disable for specific command:

{
"customTools": {
"deploy": {
"command": "npm run deploy",
"description": "Deploy application"
}
},
"commands": {
"review": {
"customTools": false
}
}
}

Note: When a command defines its own customTools, it completely replaces the root-level tools for that command (no merging).

Feature Custom Tools Dev Tools
Location Root-level customTools builtInTools registry (root or command)
Availability All commands code / exec (and ask --write)
Purpose User-defined shell commands Predefined build/test/lint + shell tools
Per-Command Yes Yes (via commands.<cmd>.builtInTools)
Parameters Yes Limited (run_single_test only)

Both can be used together:

{
"customTools": {
"deploy": {
"command": "npm run deploy",
"description": "Deploy application"
}
},
"commands": {
"code": {
"filesystem": "all",
"builtInTools": {
"gth_checklist": true,
"run_tests": { "command": "npm test" },
"run_lint": { "command": "npm run lint-n-fix" }
}
}
}
}

All custom tool parameters are automatically validated to prevent:

  • Shell injection: Blocks |, &, ;, `, $, $(, newlines
  • Directory traversal: Blocks .., /../, \..\\
  • Absolute paths: Only relative paths allowed
  • Null bytes: Blocks \0 characters

Example of a secure custom tool that accepts a file path:

{
"customTools": {
"process_file": {
"command": "node scripts/process.js ${filePath}",
"description": "Process a file in the project",
"parameters": {
"filePath": {
"description": "Relative path to the file to process"
}
}
}
}
}
{
"llm": {
"type": "anthropic",
"model": "claude-sonnet-4-5"
},
"customTools": {
"deploy_staging": {
"command": "npm run deploy:staging",
"description": "Deploy to staging environment"
},
"run_migration": {
"command": "npm run migrate -- ${name}",
"description": "Run a database migration",
"parameters": {
"name": {
"description": "Migration name"
}
}
}
},
"commands": {
"pr": {
"customTools": {
"validate_pr": {
"command": "npm run validate:pr",
"description": "Run PR validation checks"
}
}
},
"review": {
"customTools": false
},
"code": {
"filesystem": "all",
"builtInTools": {
"gth_checklist": true,
"run_tests": { "command": "npm test" },
"run_lint": { "command": "npm run lint-n-fix" }
}
}
}
}

Some parameters legitimately require values that would normally be blocked by validation. For example, deploying to a hardware device via /dev/ttyUSB0 requires an absolute path. The allow property on individual parameters lets you specify which checks to skip:

{
"customTools": {
"deploy_lesson": {
"command": "mpremote connect ${usbDevice} fs cp ${lesson} :main.py",
"description": "Deploy lesson to the robot.",
"parameters": {
"usbDevice": {
"description": "USB device of robot. Use `/dev/ttyUSB0` unless advised to use other device.",
"allow": ["absolute-paths"]
},
"lesson": {
"description": "Lesson to deploy, for example `fixed/lesson2/Move_Forward.py`"
}
}
}
}
}

In this example, only the usbDevice parameter allows absolute paths, while lesson is still validated normally.

Available allow values:

Value What it permits
absolute-paths Absolute paths like /dev/ttyUSB0 or /usr/bin/env
directory-traversal Path components containing ..
shell-injection Shell metacharacters (|, &, ;, etc.)
null-bytes Null byte characters

Checks not listed in allow remain enforced. Each parameter can have its own allow list, providing fine-grained control over validation.

allowedTools restricts an agent to an explicit allow-list of tool names. It is applied after every tool source (filesystem, built-in, custom, MCP, A2A and tools) has been resolved, so it is the only knob that can gate individual MCP and A2A tools (e.g. mcp__jira__getJiraIssue), which have no per-source override of their own.

  • omitted or undefined: no filtering, all resolved tools remain available
  • non-empty array: only tools whose name matches an entry remain available. Entries are exact tool names, or glob patterns using * as a wildcard — e.g. mcp__jira__* allows every tool from the jira MCP server without listing each one by name
  • empty array []: every tool is disabled; MCP servers are not even contacted (no OAuth), which suits agents that only need to reason over the provided prompt, such as review agents

Important: allowedTools: [] is not the same as omitting allowedTools. Use [] only when you intentionally want a tool-free agent and want to skip MCP/A2A tool discovery. Remove the property, or leave it undefined in JavaScript config, when you want all configured tools to remain available.

It can be set at the top level or per command via commands.<command>.allowedTools (the command value takes precedence):

{
"commands": {
"review": { "allowedTools": [] },
"pr": { "allowedTools": [] }
}
}

Gaunt Sloth supports middleware to intercept and control agent execution at critical points. Middleware provides hooks for cost optimization, conversation management, and custom logic.

The following predefined middleware are available (reference by name in middleware):

Reduces API costs by caching prompts (Anthropic models only):

{
"llm": {
"type": "anthropic",
"model": "claude-sonnet-4-5"
},
"middleware": ["anthropic-prompt-caching"]
}

With custom TTL configuration:

{
"middleware": [
{
"name": "anthropic-prompt-caching",
"ttl": "5m"
}
]
}

TTL options: "5m" (5 minutes) or "1h" (1 hour)

Automatically condenses conversation history when approaching token limits:

{
"middleware": ["summarization"]
}

With custom configuration:

{
"middleware": [
{
"name": "summarization",
"maxTokensBeforeSummary": 8000,
"messagesToKeep": 5
}
]
}

Configuration options:

  • maxTokensBeforeSummary: Maximum tokens before triggering summarization (default: 10000)
  • messagesToKeep: Number of recent messages to keep after summarization
  • summaryPrompt: Custom prompt template for summarization
  • model: Custom model for summarization (defaults to main LLM)

For AG-UI web clients that let the model request a photo through a frontend “capture image” tool. Such a tool runs in the browser and returns its result to the server as a tool message whose content is a JSON string {"mimeType":"image/...","data":"<base64>"}. Without this middleware the model receives that as plain text and cannot see the image. Enabling it converts the capture result into a vision message (in the shape the active provider decodes) before the next model call:

{
"middleware": ["frontend-image-injection"]
}

It is opt-in — it only runs when listed in middleware. The tool name it watches for defaults to capture_image; set toolName if your frontend tool is named differently:

{
"middleware": [
{
"name": "frontend-image-injection",
"toolName": "take_photo"
}
]
}

The provider-specific vision-block shape is chosen automatically from the configured model provider, so the same config works across Anthropic, OpenAI-compatible, Ollama, and Google providers.

You can combine multiple middleware:

{
"llm": {
"type": "anthropic",
"model": "claude-sonnet-4-5"
},
"middleware": [
"anthropic-prompt-caching",
{
"name": "summarization",
"maxTokensBeforeSummary": 12000
}
]
}

Custom Middleware (JavaScript Config Only)

Section titled “Custom Middleware (JavaScript Config Only)”

Custom middleware objects are only available in JavaScript configurations. Always wrap them with LangChain’s createMiddleware to include the required MIDDLEWARE_BRAND marker—plain objects/functions will be rejected by the registry.

.gsloth.config.mjs
import { createMiddleware } from 'langchain';
const requestLogger = createMiddleware({
name: 'request-logger',
beforeModel: (state) => {
// Custom logic before model execution
console.log('Processing request...');
return state;
},
afterModel: (state) => {
// Custom logic after model execution
console.log('Model completed');
return state;
},
});
export async function configure() {
const anthropic = await import('@langchain/anthropic');
return {
llm: new anthropic.ChatAnthropic({
model: 'claude-sonnet-4-5',
}),
middleware: ['summarization', requestLogger],
};
}

Some AI providers provide integrated server tools, such as web search.

.gsloth.config.json for OpenAI Web Search

{
"llm": {
"type": "openai",
"model": "gpt-4o"
},
"tools": [{ "type": "web_search_preview" }]
}

.gsloth.config.json for Anthropic Web Search

{
"llm": {
"type": "anthropic",
"model": "claude-sonnet-4-5"
},
"tools": [
{
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 10
}
]
}