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_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.

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.

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.

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 tools6EnableCron && 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
Subagent tools (run_agent, start/get/wait/cancel_subagent)5SubagentManager != niltools_default.go:344
Workflow tools2WorkflowService != niltools_default.go:331

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.goAllowedTools []string \json:"allowed_tools,omitempty"``


Default permissions (security)

By default, tools run without any sandboxing and without any approval prompts. This is appropriate for trusted development environments and automated pipelines where you control the workspace. For anything handling untrusted input, explicitly set a permissions policy.

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

Sandbox scope (sandbox field):

ValueBehavior
"unrestricted"No restrictions. Default.
"local"Filesystem access unrestricted; outbound network commands (curl, wget, nc, netcat, telnet) blocked in bash.
"workspace"Bash can only access paths inside the workspace directory.

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"
}
}

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