Skip to main content

Agent Tool Catalog

The tool catalog is the complete set of capabilities the coding agent can call during a run. Every tool is a Go function registered into a Registry and exposed to the LLM via JSON-schema definitions. Understanding the catalog lets you predict which tools an agent will see, enable optional tool groups by satisfying their dependencies, and scope a run to only the tools a task actually needs.

Two terms to know upfront:

  • Core tool — always sent to the LLM on every run.
  • Deferred tool — hidden by default; the agent must activate it with find_tool before it can call it. This keeps the LLM's context window from being flooded by rarely-used capabilities.

Default permission model: unrestricted. Every tool executes under the run's PermissionConfig, which defaults to {sandbox: "unrestricted", approval: "none"}. That means the agent has unrestricted filesystem access and is never asked for approval before calling a mutating tool — unless the caller opts in to a stricter policy. See Tools and Permissions for how to harden this.


How the catalog is built​

The production entry point is NewDefaultRegistryWithOptions in internal/harness/tools_default.go. It:

  1. Builds core tools from internal/harness/tools/core/ — always visible.
  2. Builds deferred tools from internal/harness/tools/deferred/ — hidden until find_tool activates them.
  3. Registers find_tool itself as a core meta-tool.
  4. Wraps every handler with htools.ApplyPolicy(...) for approval enforcement.

A separate legacy function, BuildCatalog (internal/harness/tools/catalog.go), produces a flat sorted slice and is used in non-default paths (tests, custom harnesses). Both paths share the same underlying tool implementations.

Tool tier constants (defined in internal/harness/tools/types.go):

TierConstantVisibility
CoreTierCore = "core"Always sent to the LLM
DeferredTierDeferred = "deferred"Activated via find_tool

Core tools​

These tools are visible to the agent on every run. You cannot remove them via allowed_tools (except for the three that are always available — see Per-run tool filtering below).

ToolMutatingKey parameters
readnopath (or alias file_path), offset, limit, max_bytes (default 16 KB, max 1 MB)
writeyespath, content, append, expected_version
edityespath, old_text, new_text, replace_all, expected_version
apply_patchyespatch for a unified diff; or edits array; or single find/replace via find/replace/replace_all
file_inspectnopath, preview_lines (default 20), hex_bytes (default 256)
downloadyesurl, file_path (required), timeout_seconds (default 20), max_bytes (default 50 MB). Core tier — always registered.

Deferred tools​

Deferred tools are invisible until the agent calls find_tool. Once activated, they remain available for the duration of that run. Activation is tracked per-run by ActivationTracker (internal/harness/activation.go).

To see which tools are available, the agent calls:

{
"name": "find_tool",
"input": { "query": "git history" }
}

Or to activate a specific tool by name:

{
"name": "find_tool",
"input": { "query": "select:git_log_search" }
}

Git and code intelligence​

ToolWhat it does
git_log_searchSearch commit history by message (--grep) or diff content (-S pickaxe), or both. Params: query, mode (message/pickaxe/both), path, max_results (default 20), since.
git_file_historyFile-level commit history with diff summaries.
git_blame_contextBlame annotations with surrounding context lines.
git_diff_rangeDiff between two refs.
git_contributor_contextContributor activity statistics for a file or repo.
sourcegraphSearch Sourcegraph. Requires Sourcegraph.Endpoint in server config.

LSP tools are not in the default registry. lsp_diagnostics, lsp_references, and lsp_restart require a running language server and must be wired manually — they are explicitly excluded from NewDefaultRegistryWithOptions (internal/harness/tools_default.go). Do not expect them to appear even after find_tool.

Web​

Registered when EnableAgent && EnableWebOps && WebFetcher != nil.

ToolWhat it does
web_searchKeyword web search, up to 50 results (default 5).
web_fetchFetch a single web page via WebFetcher.
agentic_fetchAgent-assisted fetch — uses AgentRunner to process the page.

Scheduling​

Registered when EnableCron && CronClient != nil.

ToolWhat it does
cron_createCreate a recurring job. Required: name, schedule (5-field UTC cron), and explicit execution_type (shell or harness). shell requires a non-empty command for headless execution and records command output in history; harness requires a non-empty prompt, rejects command, and starts an assistant continuation in the creating conversation. timeout_seconds defaults to 30.
cron_listList all cron jobs.
cron_getGet a job and its 5 most recent executions by ID.
cron_updateUpdate an existing job's schedule, command, prompt, execution config, timeout, or tags. Requires at least one field to change; use cron_pause/cron_resume to change status instead.
cron_historyList a job's execution history by job ID (names are not accepted). Params: id (required), limit, offset.
cron_deleteDelete a cron job (soft-delete).
cron_pausePause a job (sets status=paused).
cron_resumeResume a paused job (sets status=active).

Registered when EnableCallbacks && CallbackManager != nil.

ToolWhat it does
set_delayed_callbackSchedule a one-shot callback that re-invokes the agent after a delay. Min 5 s, max 1 hour, max 10 per conversation.
cancel_delayed_callbackCancel a pending delayed callback.
list_delayed_callbacksList all pending delayed callbacks for the current conversation.

Agent orchestration​

Registered when EnableAgent && AgentRunner != nil.

ToolWhat it does
agentInline sub-agent call via AgentRunner.RunPrompt.
spawn_agentSpawn a recursive child agent. Max fork depth: 5 (DefaultMaxForkDepth).
task_completeUsed by child agents to return a result to their parent. Not available at depth 0.
agent_swarmFan out a prompt to multiple sub-agents in parallel and collect their results (internal/harness/tools/swarm.go:9).

Registered when SubagentManager != nil.

ToolWhat it does
run_agentSpawn a subagent with a named profile and wait for it. Params: task, profile, model, max_steps.
start_subagentStart a subagent (fire-and-forget).
get_subagentPoll subagent status by ID.
wait_subagentBlock until a subagent completes.
cancel_subagentCancel a running subagent.
message_subagentSend a follow-up message to a running subagent by resolving its subagent ID to a run ID, then steering that run. Params: id, message.
notify_parentSend a message back to the parent agent that spawned this run. Fails if this run has no recorded parent (was not spawned as a subagent). Params: message.

MCP integration​

Registered when EnableMCP && MCPRegistry != nil.

ToolWhat it does
list_mcp_resourcesList MCP resources across all connected servers.
read_mcp_resourceRead a single MCP resource by URI.
mcp_<server>_<tool>Any tool from a connected MCP server (dynamic).

Registered when MCPConnector != nil (wired after the registry is built, independent of EnableMCP).

ToolWhat it does
connect_mcpConnect to a new HTTP/SSE MCP server mid-session. Registers its tools as mcp_<server>_<tool>.

Profile management​

Most profile tools are always registered; create_profile, update_profile, and delete_profile require ProfilesDir.

ToolWhat it does
list_profilesList available agent profiles.
get_profileGet a profile definition.
get_profile_manifestGet the effective tool manifest for a profile.
create_profileCreate a new profile TOML. Requires ProfilesDir.
update_profileUpdate an existing profile. Requires ProfilesDir.
delete_profileDelete a profile. Requires ProfilesDir.
validate_profileDry-run validate a profile definition.
recommend_profileSuggest a profile for a given task.
get_efficiency_reportReport on profile run history.

Skills and workflows​

ToolEnabling conditionWhat it does
create_skillSkillsDir != ""Author a new SKILL.md file.
verify_skillSkillVerifier != nilValidate a skill and write verification metadata.
manage_skill_packsPackRegistry configuredManage skill pack subscriptions (list/search/activate).
create_workflowWorkflowService != nilAuthor a new Go workflow. Params: name, description, source, scope.
run_workflowWorkflowService != nilRun a named workflow. Params: name, args, wait, timeout_seconds, resume_run_id.
run_recipeRecipesDir != ""Execute a multi-step recipe from a YAML file.
create_prompt_extensionalways registeredCreate a behavior or talent prompt extension.

Model catalog​

ToolWhat it does
list_modelsList, filter, and inspect available LLM models from the provider catalog. Params: action (list/info/providers), provider, model_id, tool_calling, streaming, speed_tier, cost_tier, modality, best_for, strength, min_context, reasoning.

Deployment​

Always registered; the built-in adapter registry supports railway and flyio.

ToolWhat it does
deployDeploy to, check status of, view logs for, or auto-detect a cloud platform (railway, flyio). Params: platform (auto-detected if omitted), action (deploy/status/logs/detect), workspace.

Goals​

Registered when a goals manager is configured (internal/goals).

ToolWhat it does
goalsManage persistent, multi-session goals with dependency chains and progress tracking. Actions: create (with optional depends_on), get, update (status/progress/metadata), list (with status filter), ready (goals whose dependencies are all completed). Goals survive restarts and support verification criteria for definition-of-done enforcement.

Activation and naming​

find_tool — the gateway to deferred tools​

find_tool is a core meta-tool, so it is always in the LLM's context. It accepts either:

  • query — keyword search over deferred tool names, descriptions, and tags.
  • select:<name> — directly activate a tool by exact name.

Once find_tool activates a tool, it becomes visible in subsequent LLM turns for that run only.

A tool.activated event type (constant EventToolActivated) is reserved for this activation, but it has no confirmed production emission site today — do not build consumers that depend on receiving it. See the Event Catalog.

MCP tool naming​

When an external MCP server is connected (globally at startup or per-run via mcp_servers), each of its tools is registered with the name:

mcp_<server>_<tool>

Both <server> and <tool> are sanitized: lowercased, and -, /, ., and space are replaced with _. For example, the read_file tool on the filesystem server becomes mcp_filesystem_read_file.

Implementation: internal/harness/registry.go

toolName := "mcp_" + safeServer + "_" + safeName

MCP tools are always TierDeferred and are tagged ["mcp", "integration", "external", "dynamic", "mcp_server:<serverName>"].

The runbook at docs/runbooks/mcp.md incorrectly states the format as {server_name}__{tool_name} (double underscore). The code uses mcp_{server}_{tool} (single underscore with mcp_ prefix). Trust the code.

AlwaysAvailableTools​

Three tools always bypass the AllowedTools filter and any active skill constraint:

var AlwaysAvailableTools = map[string]bool{
"AskUserQuestion": true,
"find_tool": true,
"skill": true,
}

Source: internal/harness/skill_constraint.go


Enabling conditions​

Tool groups are gated by flags and runtime dependencies. A group is silently absent when its condition is not met — there is no error.

Tool groupCountEnabling conditionSource
Cron tools8EnableCron && CronClient != nilcatalog.go:86, tools_default.go:273
Callback tools3EnableCallbacks && CallbackManager != nilcatalog.go:97, tools_default.go:283
LSP tools3EnableLSP (not in default registry)catalog.go:57
Sourcegraph1Sourcegraph.Endpoint != ""catalog.go:60
MCP toolsdynamicEnableMCP && MCPRegistry != nilcatalog.go:63
SkillsvariesEnableSkills && SkillLister != nilcatalog.go:74
Web ops3EnableAgent && EnableWebOps && WebFetcher != nilcatalog.go:82
Recipes1RecipesDir != ""tools_default.go:298
Agent tools (agent, spawn_agent, task_complete)3EnableAgent && AgentRunner != niltools_default.go:256
agent_swarm1AgentSwarmRunner != niltools_default.go:549
Subagent tools (run_agent, start/get/wait/cancel_subagent, message_subagent, notify_parent)7SubagentManager != nil (plus RunSteerer != nil for message_subagent/notify_parent)tools_default.go:535-558
Workflow tools2WorkflowService != niltools_default.go:331
list_models1ModelCatalog != niltools_default.go:461-463
deploy1always registeredtools_default.go:601
goals1GoalManager != niltools_default.go:604-609

Recipes vs workflows vs skills — what's the difference?​

These three abstractions are related but distinct:

Recipe

A declarative YAML file that defines a named sequence of tool calls (steps). Each step specifies a tool name and static args; {{variable}} placeholders are substituted at execution time. Recipes live in RecipesDir (env: HARNESS_RECIPES_DIR) and are executed by the run_recipe deferred tool. They are the right choice for deterministic, repeatable multi-step sequences that you want to author without writing Go code.

Workflow (Go workflow)

A compiled Go bundle registered with the workflow engine (internal/workflow/). Workflows use primitives like ctx.Agent(), ctx.Parallel(), and ctx.Pipeline() to compose sub-agents and stages. They support full Go logic, budget tracking, and schema validation. Executed via the run_workflow deferred tool or the POST /v1/script-workflows/{name}/runs HTTP route.

Skill

A prompt module — a SKILL.md file with YAML frontmatter and a Markdown body. When invoked, the skill's body is injected into the conversation (or spawned in a forked subagent for context: fork skills). Skills constrain the agent's tool access via allowed-tools for the duration of the invocation. See Skills.


Per-run tool filtering​

To restrict the tools available to a specific run, pass allowed_tools in the POST /v1/runs body:

{
"prompt": "Review this PR for security issues",
"allowed_tools": ["read", "grep", "glob", "ls", "git_diff"]
}

When allowed_tools is non-empty, only the listed names plus AlwaysAvailableTools (AskUserQuestion, find_tool, skill) are offered to the LLM. An empty or omitted list means all tools are available.

Source: internal/harness/types.go — AllowedTools []string \json:"allowed_tools,omitempty"``


Default permissions (security)​

By default, tools run sandbox-confined to the workspace directory and without any approval prompts (no sandboxing at all requires explicitly opting into "unrestricted"). Bash's outbound network access is unrestricted by default too — set network: "deny" to block it. For anything handling untrusted input, review the permissions policy explicitly rather than relying on defaults.

The PermissionConfig struct (source: internal/harness/types.go) controls three independent axes:

Sandbox scope (sandbox field):

ValueBehavior
"workspace"Bash can only access paths inside the workspace directory. Default.
"local"Filesystem access unrestricted.
"unrestricted"No restrictions.

Network policy (network field, issue #1397):

ValueBehavior
"allow"Bash's outbound network access is unrestricted. Default (an omitted or empty network field behaves the same as "allow").
"deny"Bash's outbound network access is denied at the OS level (seatbelt on macOS, bubblewrap on Linux) for "workspace" and "local" sandbox scopes. "unrestricted" scope is never network-confined regardless of this field.

Approval policy (approval field):

ValueBehavior
"none"Never ask for approval. Default.
"destructive"Require operator approval before mutating tool calls (writes, bash, etc.).
"all"Require approval before every tool call.

To harden a run, pass a permissions object in the run request:

{
"prompt": "...",
"permissions": {
"sandbox": "workspace",
"approval": "destructive",
"network": "deny"
}
}

Approval requests flow through ApprovalBroker (internal/harness/approval_broker.go) and are resolved via POST /v1/runs/{id}/approve or POST /v1/runs/{id}/deny. See Tools and Permissions for the full approval workflow.


Next steps​