Built-in tools the model can call. Plugin tools are specified in 09-plugins.md.
Package: internal/tool/
Each tool is defined by a Def struct:
type Def struct {
ID string
Description string
Parameters map[string]any // JSON Schema
Permission string // permission category
Execute func(ctx, tc, args) // handler
}Tool context (Context) provides: SessionID, Directory, Perms (permission service), Bus (event bus), JobManager (subagent jobs), SubagentDepth, DB (SQLite handle), AfterHook (post-execution modifier).
Tools are registered in a thread-safe Registry (sync.RWMutex) that preserves insertion order.
- Look up tool by name; return error if not found or disabled
- Create fresh
Contextwith session-specific fields - If permission service exists and tool has a non-empty
Permission, callPerms.Ask()(with agentRulesetwhen set) - Publish
tool.execute.beforebus event - Call
def.Execute(ctx, toolCtx, args) - On error: publish
tool.execute.afterwithsuccess: false, return error string as output - On success: apply
AfterHookif set, publishtool.execute.after - Truncate via
TruncPreview(output)(head+tail preview) - Return
(output, isError, nil)
RegisterBuiltins(r) registers 18 always-available tools (including plan_enter/plan_exit). RegisterConditional(r, cfg) adds conditional tools:
| Tool | Condition |
|---|---|
skill |
ConfigDir or ProjectDir is non-empty |
websearch |
EXA_API_KEY environment variable is set |
Source: builtin.go
Source: truncate.go
| Constant | Value | Description |
|---|---|---|
MaxLines |
2,000 | Maximum output lines |
MaxBytes |
51,200 (50 KB) | Maximum output bytes |
Truncation direction:
TruncHead— keep tail, prepend"... [truncated N bytes, showing last M lines] ..."TruncTail— keep head, append"... [truncated N bytes, showing first M lines] ..."
Registry path: non-error tool output is truncated via TruncPreview (head+tail preview), not Truncate(..., TruncTail).
Byte truncation respects UTF-8 rune boundaries.
Read files with line numbers. Source: read.go
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file_path |
string | yes | — | Absolute path |
offset |
integer | no | 0 | Line offset (0-indexed) |
limit |
integer | no | 2000 | Max lines to read |
| Constant | Value |
|---|---|
defaultReadLimit |
2,000 lines |
maxLineLength |
2,000 characters (truncated with "...") |
binaryCheckSize |
8,192 bytes |
binaryThreshold |
0.30 (30% non-text bytes) |
Behaviors:
- Output format:
N\tcontent\n(numbered lines) - Image support:
.jpg,.jpeg,.png,.gif,.webpreturned as base64 data URIs - Binary detection: checks first 8,192 bytes; null bytes count as 10 non-text bytes
- On file-not-found:
suggestSimilar()finds files with >= 3 common prefix characters
Permission: read
Create or overwrite files. Source: write.go
| Parameter | Type | Required | Description |
|---|---|---|---|
file_path |
string | yes | Absolute path |
content |
string | yes | File content |
Behaviors:
- Creates parent directories with
os.MkdirAll(dir, 0755) - Line ending preservation: if existing file has CRLF, converts new content to CRLF
- Writes with mode
0644 - When
formatteris enabled, runs the matching formatter after the write. A failure is appended to the tool output and does not undo the write.editandapply_patchupdates do the same - Publishes
file.modifiedbus event with operation"write"
Permission: edit
Search-and-replace editing with 10-strategy fuzzy cascade. Source: edit.go, edit_replacer.go
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file_path |
string | yes | — | Absolute path |
old_string |
string | yes | — | Text to find |
new_string |
string | yes | — | Replacement text |
replace_all |
boolean | no | false | Replace all occurrences |
Cascade replacement strategies (tried in order until one succeeds):
| # | Strategy | Behavior |
|---|---|---|
| 1 | SimpleReplacer | Exact strings.Replace |
| 2 | LineTrimmedReplacer | Trim whitespace per line |
| 3 | BlockAnchorReplacer (strict) | First/last lines exact, interior similarity >= 1.0 |
| 4 | WhitespaceNormalizedReplacer | Collapse all whitespace to single space |
| 5 | IndentationFlexibleReplacer | Strip common indent, match ignoring indent level |
| 6 | EscapeNormalizedReplacer | Normalize \r\n → \n, \t → spaces |
| 7 | TrimmedBoundaryReplacer | Strip blank leading/trailing lines |
| 8 | ContextAwareReplacer | Non-whitespace lines as anchors (>= 3 lines, >= 2 anchors) |
| 9 | BlockAnchorReplacer (relaxed) | Interior similarity >= 0.7 (threshold 0.3) |
| 10 | MultiOccurrenceReplacer | Fuzzy scoring >= 0.8, picks best match |
Per-file mutex: fileMutexMap serializes edits to the same path. Cleanup at fileMutexCleanupThreshold = 1000 entries.
Levenshtein: 2-row DP algorithm in edit_levenshtein.go. Similarity() = 1.0 - dist/maxLen.
On no match: fuzzyFind() searches for the first line of the needle and shows "did you mean" context.
Permission: edit
Shell command execution. Tool ID is bash (not shell). Source: shell.go
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
command |
string | yes | — | Shell command |
timeout |
integer | no | 120,000 ms | Timeout in milliseconds (max 600,000) |
description |
string | no | — | Human-readable description |
| Constant | Value |
|---|---|
defaultShellTimeout |
120 seconds |
maxShellTimeout |
600 seconds (10 minutes) |
Destructive command detection (triggers destructive-shell permission check):
rm -rf/rm --recursivevariantsgit push --force,git reset --hard,git clean -f,git branch -DDROP TABLE/DATABASE,TRUNCATE TABLEkill -9,mkfs,dd,> /dev/sd*reboot,shutdown,poweroff,haltoc/kubectlmutating verbs (apply,create,delete,replace,patch,scale,rollout,exec,drain,cordon, and the others inIsDestructive).get,describe,logs, andoc adm topstay read-onlyhelm install,upgrade,uninstall,rollback,delete- Host changes:
systemctlstart/stop/restart/reload/enable/disable/mask, account commands (useradd,passwd,visudo),setenforce/setsebool, firewall edits, and address or route changes. The same verbs ask when they appear afterssh.systemctl statusand firewall listings stay read-only
Secret file detection (asks with secret-shell when a permission service is present; blocked when it is not): .env, .env.*, credentials, *.key, *.pem
Executes via sh -c <command> in tc.Directory. Combines stdout/stderr (stderr prefixed with "STDERR:\n").
Permission: shell (agent allowlists may use bash or shell interchangeably)
Regex content search. Source: grep.go
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
pattern |
string | yes | — | Regex pattern |
path |
string | no | working dir | Search root |
include |
string | no | — | File glob filter (e.g., *.go) |
max_count |
integer | no | 500 | Max matches |
| Constant | Value |
|---|---|
maxGrepMatches |
500 |
| Max file size | 1 MB (skipped if larger) |
Sorted by file modification time (newest first). Output: relPath:lineNum:lineContent. Skips: .git, node_modules, vendor, .tinycode.
Permission: read
File pattern matching. Source: glob.go
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
pattern |
string | yes | — | Glob pattern (supports **) |
path |
string | no | working dir | Search root |
| Constant | Value |
|---|---|
maxGlobResults |
1,000 |
Two modes: simple glob (no **) uses filepath.Glob; double glob (**) uses custom recursive walkDoubleGlob. Sorted by modification time (newest first). Same skip directories as grep.
Permission: read
Ask the user a question. Source: question.go
| Parameter | Type | Required | Description |
|---|---|---|---|
question |
string | yes | Question text |
options |
string[] | no | Optional choices |
Publishes question.asked event, subscribes to question.replied, and blocks until answer arrives (or context cancellation). The TUI displays a full-screen question overlay with text input (or option selection when choices are provided) and posts POST /question/{id}/reply. ACP and headless modes relay through their respective permission/event channels.
Permission: read
Fetch URL content with SSRF protection. Source: webfetch.go
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url |
string | yes | — | URL to fetch |
method |
string | no | GET | HTTP method |
headers |
object | no | — | Additional headers |
body |
string | no | — | Request body |
format |
enum | no | text | text, markdown, html |
| Constant | Value |
|---|---|
maxFetchSize |
5 MB |
fetchTimeout |
30 seconds |
| Max redirects | 10 |
| TLS minimum | TLS 1.2 |
SSRF protection: Pre-flight DNS check against blocked CIDRs: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 0.0.0.0/8, ::1/128, fc00::/7, fe80::/10; dial-time IP checks in ssrfSafeTransport; CheckRedirect limits redirects and schemes. Full scope/non-goals: 15-security.md §15.6.
Permission: webfetch
Web search via Exa API. Source: websearch.go
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string | yes | — | Search query |
num_results |
integer | no | 8 | Number of results |
Conditional: Only registered when EXA_API_KEY env var is set. API endpoint: https://api.exa.ai/search. Timeout: 30 seconds.
Permission: read
Spawn subagent sessions. Source: task.go
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
description |
string | yes | — | Short task description |
prompt |
string | yes | — | Task prompt |
subagent_type |
string | no | — | Agent type for subagent |
task_id |
string | no | — | Resume existing task |
background |
boolean | no | false | Run in background |
| Constant | Value |
|---|---|
maxSubagentDepth |
5 |
Status check mode: When task_id is provided, returns JSON with job status/result.
Permission: shell
Manage session todo list. Source: todowrite.go
| Parameter | Type | Required | Description |
|---|---|---|---|
todos |
array | yes | Array of {content, status, priority} |
Status: pending, in_progress, done. Priority: high, normal, low. Replaces all existing todos. Persisted to SQLite. Publishes session.todos.updated event.
Permission: edit
Execute a skill by name. Source: skill.go
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Skill name or ID |
arguments |
string | no | Space-separated arguments ($1, $2, $ARGUMENTS) |
Conditional: Registered when ConfigDir or ProjectDir is non-empty. Discovers skills via skill.Discover(). On not-found, lists all available skills.
Permission: read
Switch the session into plan mode. Source: plan_switch.go.
No parameters. On approval, records a pending switch to the plan agent (via a shared SafeAgentSwitch); the session processor applies it after the current tool round (Processor.applyPendingAgentSwitch), publishes session.agent.switched, and the caller persists the new agent so the next turn resolves plan's permissions and system prompt.
Permission: plan_enter (native build agent allows it by default; other agents fall back to ask)
Switch the session back to the build agent. Source: plan_switch.go.
No parameters. Same approval/apply flow as plan_enter, targeting build instead of plan.
Permission: plan_exit (native plan agent allows it by default; other agents fall back to ask)
Fallback for malformed tool calls. Source: invalid.go
| Parameter | Type | Required | Description |
|---|---|---|---|
error |
string | yes | JSON parse error |
original_name |
string | yes | Original tool name |
original_args |
string | no | Raw arguments |
Never exposed to the LLM — ToolDefs() explicitly filters out name == "invalid". Always returns IsError: true.
Permission: read
| Category | Tools |
|---|---|
read |
read, grep, glob, question, websearch, invalid, skill |
edit |
write, edit, apply_patch, todowrite |
shell |
bash, task, monitor |
webfetch |
webfetch |
plan_enter |
plan_enter |
plan_exit |
plan_exit |
destructive-shell |
(secondary check within bash/monitor for destructive commands) |
secret-shell |
(secondary check within bash/monitor and session shell for commands that name a secret file) |
Shared between grep and glob (glob.go):
var skipDirs = map[string]bool{
".git": true, "node_modules": true, "vendor": true, ".tinycode": true,
}Source: internal/llm/openai.go
When the LLM generates invalid JSON for tool call arguments:
- Strip markdown fences (
```json ... ```) - Remove trailing commas
- Route to
invalidtool if repair fails