# 05 — Built-in Tools `harness-tools`. All parameter structs derive `schemars::JsonSchema` (single source of truth for the schema sent to the model). Invalid-params errors are returned to the model as tool errors with the schema echoed (opencode's `invalid` pattern). ## Shared: output truncation `truncate.rs`: cap output at 30,000 chars; keep head + tail with `... [N chars truncated; full output: /tool-output/.txt] ...`; full output spilled to the session data dir. ## Tool table | Tool | Params | Behavior | Permission key / pattern | |---|---|---|---| | `bash` | `{command, timeout_ms?, cwd?, description}` | `tokio::process::Command` `sh -c`, own process group (`setsid`); kill group on cancel/timeout (default 2 min, max 10); merge stdout+stderr; truncate | `bash` / command string; "always" pattern = ` *` via `shell-words` | | `read` | `{file_path, offset?, limit?}` | 2000-line default window, `cat -n` numbering, 2000-char line clamp, binary/image detection; reports context files to the JobBoard when in a child session | `read` / worktree-relative path (bundled default: allow) | | `edit` | `{file_path, old_string, new_string, replace_all?}` | replacer chain (below); CRLF/BOM preservation; per-file `tokio::Mutex` lock map; unified diff via `similar` in permission metadata + part metadata; LSP diagnostics appended | `edit` / relative path; always-pattern `*` | | `write` | `{file_path, content}` | mkdir -p; diff vs existing in metadata; LSP diagnostics | `edit` (same key as opencode) / relative path | | `glob` | `{pattern, path?}` | `ignore::WalkBuilder` (respects .gitignore) + `globset`; sort mtime desc; cap 100 | none (read-only) | | `grep` | `{pattern, path?, include?}` | `grep-regex` + `grep-searcher`, gitignore-aware; cap 100 matches, sorted by file mtime | none | | `todowrite` / `todoread` | list of `{content, status: pending\|in_progress\|completed, priority}` | stored on the session row (JSON); part metadata drives a TUI checklist | none | | `webfetch` | `{url, format: text\|markdown\|html, timeout?}` | reqwest GET (redirect cap 5), 5 MB cap, HTML→markdown via `htmd`, truncate | `webfetch` / URL | | `task` | see [04-multiagent.md](04-multiagent.md) | spawn/reuse subagent sessions | `task` / agent name | | `skill` | `{name}` | returns the SKILL.md body on demand | `skill` / name (default allow) | MCP tools are registered alongside these, namespaced `{server}_{tool}` — see [09-integrations.md](09-integrations.md). ## Edit tool: replacer chain **Port opencode's replacer chain verbatim** from `~/repos/opencode/packages/opencode/src/tool/edit.ts`, including its test table (`edit.test.ts`) — this is the highest-regression-risk code in the project: 1. Simple (exact match) 2. LineTrimmed 3. BlockAnchor (first/last line anchors, ≥0.65 similarity on middle lines, Levenshtein) 4. WhitespaceNormalized 5. IndentationFlexible 6. EscapeNormalized 7. TrimmedBoundary 8. ContextAware 9. MultiOccurrence (with `replace_all`) Plus the disproportionate-match guard (reject matches that differ too much in size from `old_string`). ## Registry & agent filtering `ToolRegistry::for_agent(agent)`: 1. Filter by the agent's `tools` wildcard map (`{ "write": false, "mcp_*": false }`). 2. Hard-remove tools denied for the session depth (e.g. `task`, `todowrite` at depth > 0 by default). 3. The `task` tool's description is generated dynamically, listing available subagents with their descriptions (so routing knowledge comes from agent frontmatter, not hardcoded text).