| 1 | # Tool Contract |
| 2 | |
| 3 | Structured file mutations require a live host observation of the target's |
| 4 | current version. Any successful text window establishes it; reading coverage |
| 5 | and whole-file completion are not host gates. See [File observation |
| 6 | lifecycle](READ_EVIDENCE_LIFECYCLE.md). |
| 7 | |
| 8 | <a href="./TOOL_CONTRACT.zh-CN.md">简体中文</a> |
| 9 | |
| 10 | This document records the Reasonix compile-time built-in tool contract. The |
| 11 | provider surface is selected once when the session boots: POSIX hosts expose |
| 12 | `bash`; Windows hosts expose `pwsh`. Compatibility aliases remain executable |
| 13 | for old session replay but are omitted from new provider schemas. |
| 14 | |
| 15 | | Tool | Read-only | Description | |
| 16 | | --- | --- | --- | |
| 17 | | `bash` | false | Execute a command in the shell and return combined stdout/stderr. Use for builds, tests, git, package managers, etc. To search/read/list/edit/move files, prefer the dedicated tools (grep, read_file, ls, glob, edit_file, move_file) over shell grep/cat/ls/find/sed/mv/Move-Item - they behave identically on every OS. For symbol search or architecture questions, prefer LSP/read tools and targeted grep before shell commands. | |
| 18 | | `pwsh` | false | Windows-only provider shell. Execute one PowerShell command in an isolated process. `description` is required for new calls; `timeout_ms` applies only to foreground work; `run_in_background=true` returns a `pwsh-*` job id. Use PowerShell 5.1-compatible `;` and `if ($?) {}` syntax. | |
| 19 | | `bash_output` | true | Hidden compatibility alias for old sessions. New calls use `job_output`. | |
| 20 | | `code_index` | true | Lightweight built-in code symbol index. Prefer lsp_* for language semantics and installed code graph MCP tools for call graph, impact, and architecture relationships; use this as the local fallback for file outlines and symbol definition candidates, then verify with read_file or grep. | |
| 21 | | `compress` | true | Compress a selected part of the current model-visible conversation without deleting visible history. Use only when the user explicitly asks for context compression. Choose `before` to summarize everything before the uniquely matched user turn while keeping that turn and later context, or `after` to summarize from that turn through the last completed turn while keeping the active turn. The anchor must be an exact, unique excerpt from a real user message; use a longer excerpt if the tool reports multiple matches. | |
| 22 | | `create_goal` | false | Create and activate one long-running goal from a directly authorized human turn. Omitting or setting max_goal_rounds to null means unlimited automatic rounds. It never overwrites an unfinished goal. | |
| 23 | | `delete_range` | false | Delete a contiguous text range from a file using exact start/end text anchors. Each anchor must match exactly one line. Returns unified diff on success. Use for large deletions - smaller changes should use edit_file. | |
| 24 | | `delete_symbol` | false | Delete a named symbol (function, method, type, interface, const, var) from a Go source file using AST parsing. For non-Go files, use delete_range with manual anchors. | |
| 25 | | `edit_file` | false | Replace an exact string in a file with another. old_string must occur exactly once; add surrounding context to disambiguate. Use for targeted edits instead of rewriting the whole file. | |
| 26 | | `glob` | true | Find files matching a glob pattern (e.g. "*.go", "internal/*/*.go", "**/*.test.ts"). Supports shell metacharacters * ? [] and the recursive ** pattern. Independent globs with no data dependency should be issued in the same round. | |
| 27 | | `get_goal` | true | Read the current goal together with its live activation and stop reason. Returns goal: null when the session has no current goal. | |
| 28 | | `grep` | true | Search for a regular expression in a file, or recursively under a directory (skips hidden files and files matched by .gitignore). Returns matching lines as path:line:text, capped at 200 matches. Independent searches with no data dependency should be issued in the same round. | |
| 29 | | `job_kill` | false | Request cancellation of a running background job by job id. Returns immediately; the process tree settles as killed once shutdown completes. | |
| 30 | | `job_output` | true | Read output from a background job. Reads are non-blocking unless wait=true; every response includes the current status. Do not busy-poll a running job. | |
| 31 | | `kill_shell` | false | Hidden compatibility alias for old sessions. New calls use `job_kill`. | |
| 32 | | `ls` | true | List the entries of a directory. Directories are shown with a trailing slash; files show their byte size. Set recursive=true to list all nested files depth-first (skips .git/node_modules). Independent directory reads with no data dependency should be issued in the same round. | |
| 33 | | `move_file` | false | Move or rename a file from source_path to destination_path. Creates the destination parent directory as needed. Use instead of shell mv, Move-Item, or ren for file moves so workspace confinement and file-edit permissions apply. | |
| 34 | | `multi_edit` | false | Apply a list of edits to a single file atomically: each edit runs against the result of the previous one, all in memory; the file is rewritten only if every edit succeeds. Cheaper and safer than chaining edit_file calls - a failure in step 3 leaves the file untouched instead of half-edited. | |
| 35 | | `notebook_edit` | false | Edit one cell of a Jupyter notebook (.ipynb). Target a cell by 0-based cell_number (or cell_id). edit_mode: "replace" (default) swaps the cell's source; "insert" adds a new cell after cell_number (use -1 to prepend at the top), taking cell_type and new_source; "delete" removes the cell. cell_type is "code" or "markdown" (required for insert). Editing a code cell clears its outputs. Prefer this over edit_file for notebooks - it keeps the JSON valid. | |
| 36 | | `present` | true | Declare 1 to 8 existing files as user-facing deliverables after writing them and before the final answer. The host validates every path atomically and records only file paths and optional descriptions; it does not copy, execute, upload, or expose file bytes to the model result. | |
| 37 | | `read_file` | true | Read one bounded text window with optional line offset/limit. Output prefixes each line with its 1-based number. Any successful window observes the current file version for later structured edits. Use the next-window hint to page only when more content is useful. Legacy intent and cursor fields are accepted as navigation hints and never create a whole-file completion requirement. | |
| 38 | | `todo_write` | true | Replace the current model-maintained task list. Todo states describe progress without serial execution or host signoff requirements. | |
| 39 | | `update_goal` | false | Apply an exact goal ID/revision lifecycle action: edit, pause, resume, complete, or blocked. Direct human turns may use every action; the exact automatic goal round may only complete or block its own goal. The retired continue protocol is rejected. | |
| 40 | | `view_image` | true | Read a local PNG, JPEG, GIF, or WebP image by path and return visual content through native vision or the configured image-understanding model. Use this for image paths instead of read_file. Maximum file size: 3 MiB; maximum dimensions: 40 million pixels. | |
| 41 | | `wait` | true | Hidden compatibility alias for old sessions. New calls use `job_output(wait=true)`. | |
| 42 | | `web_fetch` | true | Fetch a URL over HTTPS/HTTP and return its text content. HTML pages are reduced to readable text; JSON / plain text / markdown bodies come back verbatim. Use to read documentation pages, API responses, or source files hosted somewhere the local filesystem can't reach. | |
| 43 | | `write_file` | false | Create or replace a text file. A missing target is created without overwriting a concurrent creator. Replacing an existing target requires a current host observation from read_file or a prior successful structured mutation. | |
| 44 | |
| 45 | ## Schema Snapshot |
| 46 | |
| 47 | The exact canonical schemas are intentionally tested in code rather than copied by hand here. Run: |
| 48 | |
| 49 | ```bash |
| 50 | go test ./internal/tool -run TestBuiltinToolContractDocumentation |
| 51 | ``` |
| 52 | |
| 53 | The test checks that every registered built-in tool has a documented name, read-only flag, description row, and canonical schema generated by `tool.BuiltinContractEntries`. |
| 54 | |
| 55 | ## Default Full Boot Surface |
| 56 | |
| 57 | In a default full-token boot, Reasonix sends the built-in tools above plus the |
| 58 | session, memory, skill, subagent, LSP, install, and slash-command tools below: |
| 59 | |
| 60 | Every session uses this exact executor tool surface plus one stable |
| 61 | proxy, `use_capability`, so optional MCP servers (including `auto_start=false`) |
| 62 | can be inspected and called without changing provider-visible schemas |
| 63 | mid-session. The model chooses verification, review, and completion from task context. The host enforces action permissions, preapproval Plan write restrictions, sandboxing, leases, and structured-file stale-version protection. An ordinary tool failure does not skip later independent calls in the same batch. |
| 64 | |
| 65 | ## Unified Boot Surface |
| 66 | |
| 67 | Every session uses the same provider-visible core tools and the same |
| 68 | `use_capability` proxy. |
| 69 | |
| 70 | The two-model Planner and all task/fleet sub-agents also use `use_capability` |
| 71 | (and never direct `mcp__*` schemas). Planner and ordinary writer-capable |
| 72 | sub-agents may call installed or project-configured MCP without |
| 73 | `readOnlyHint`; Planner leaves `destructiveHint` tools for the Executor, while |
| 74 | ordinary sub-agents use the trusted MCP path (live authorization plus explicit |
| 75 | deny only). Writer/destructive calls are still serialized and recorded as |
| 76 | execution facts for workspace leases and UI display. Strict read-only sub-agents |
| 77 | share the same proxy schema and Host connections but still require |
| 78 | `readOnlyHint` and non-destructive at execution time. Dual-model |
| 79 | attaches independent proxy frontends to both Planner and Executor so a |
| 80 | capability discovered during planning remains directly callable after handoff; |
| 81 | their ledgers/audits are isolated while Host connections are shared. A |
| 82 | single-model session has no independent Planner. |
| 83 | |
| 84 | `use_capability` is a fixed-schema proxy: prefer `search(query)` then `inspect` |
| 85 | one exact id then `call`. `action=list` is a compact diagnostic inventory. |
| 86 | Independent `list`/`search`/`inspect` calls are read-only and may be issued |
| 87 | together. Resolution is side-effect free: `action=list` returns compact, |
| 88 | sorted configured MCP server summaries without expanding every cached tool |
| 89 | description or starting any server. Use `action=inspect` on one enabled |
| 90 | `mcp-server:<name>` to read that server's live or cached tool directory without |
| 91 | starting it. `action=call` on a |
| 92 | not-yet-connected server resolves to a deferred target, Plan re-checks only an |
| 93 | explicit phase opt-out on the real target, and the server process starts only |
| 94 | after the permission gate and PreToolUse hooks approve the call. On-demand children |
| 95 | share the session lifetime (they outlive the starting call and exit with the |
| 96 | session); `action=inspect` lists live tools for connected servers and cached |
| 97 | schemas otherwise, never starting a process. First discovery of a server with |
| 98 | no schema cache goes through `action=call` on the `mcp-server:` id itself: it |
| 99 | resolves to a gated connect (permission name = the server's dedicated |
| 100 | `mcp_connect__<server>` identity, so an exact rule such as |
| 101 | `deny = ["mcp_connect__github"]` blocks process startup) that connects after |
| 102 | approval and returns the live tool directory. MCP tool rules remain exact; |
| 103 | `mcp__github__*` is not a tool-name glob. Installing an MCP authorizes the |
| 104 | Planner to use its non-destructive tools; third-party servers that omit |
| 105 | `destructiveHint` are treated as user-install trust. Before every connect or |
| 106 | `tools/call`, the frontend re-checks the current runtime enablement, |
| 107 | authorization, and exact Host connection identity; another project/tab's |
| 108 | same-name shared client is rejected without process, network, or tool dispatch. |
| 109 | |
| 110 | The fixed proxy's provider-visible name, description, schema, and ordering do |
| 111 | not change when MCP inventory changes. |
| 112 | |
| 113 | When the current frontend has a session reader, the same fixed proxy also lists |
| 114 | the read-only `session:tool_result` capability. It pages the complete local copy |
| 115 | of one tool result by UTF-8 byte offset without adding a top-level schema. Calls |
| 116 | require `tool_call_id`; new truncation markers also provide a stable |
| 117 | `result_ref`, which is required to disambiguate repeated call IDs. `offset` |
| 118 | defaults to 0, `limit` defaults to 16KiB and is capped at 24KiB. Each response |
| 119 | starts with `result_ref`, actual offset, `next_offset`, `total_bytes`, full |
| 120 | SHA-256, and `complete`, followed by the raw page. The reader is bound to the |
| 121 | current Agent session and is not inherited from a parent when a capability |
| 122 | frontend is cloned. A restricted child that already has `use_capability` may |
| 123 | read only its own results; an allowed-tools profile without the proxy is not |
| 124 | widened. |
| 125 | |
| 126 | `ask`, `docs`, `explore`, `fleet`, `forget`, `history`, `install_skill`, `install_source`, |
| 127 | `list_sessions`, `lsp_definition`, `lsp_diagnostics`, `lsp_hover`, |
| 128 | `lsp_references`, `memory`, `parallel_tasks`, `read_only_skill`, |
| 129 | `read_only_task`, `read_session`, `read_skill`, `read_subagent_result`, `remember`, `research`, |
| 130 | `review`, `run_skill`, `security_review`, `slash_command`, `task`. |
| 131 | |
| 132 | `parallel_tasks` and `fleet` keep their combined result below the single-tool |
| 133 | output limit by returning a fair preview and a stable `Subagent reference` for |
| 134 | every persisted child. `read_subagent_result` pages through one referenced |
| 135 | final answer by UTF-8 byte offset, so long parallel research remains lossless |
| 136 | without injecting every report into the parent context at once. References are |
| 137 | restricted to the current conversation lineage and workspace. |
| 138 | |
| 139 | Persisted child results also carry an explicit `status` (`completed`, `partial`, |
| 140 | `failed`, or `cancelled`) and `retryable` flag. A partial or failed child may |
| 141 | include its last visible answer and reference; use `read_subagent_result` for |
| 142 | inspection and the original `task`/`run_skill` `continue_from` parameter for a |
| 143 | retryable continuation. `session:tool_result` is only for ordinary tool output. |
| 144 | |
| 145 | `use_capability` (`action` = `list` | `inspect` | `call` | `decline`) is on the |
| 146 | provider-visible surface for every task. Host verification obligations come |
| 147 | from real tool actions, not from preclassifying the prompt. Optional tools stay registered for host dispatch but are not |
| 148 | expanded into the top-level provider schema; the model reaches them through |
| 149 | `use_capability` without cache-breaking schema churn. |
| 150 | |
| 151 | `internal/boot.TestBootToolContractMatchesProviderVisibleSurface` verifies the |
| 152 | actual boot registry contract against the provider request, including read-only |
| 153 | flags and canonical schemas. |
| 154 | |
| 155 | ## Unified Boot Surface (every task) |
| 156 | |
| 157 | Every task starts with the same lean provider-visible core: direct |
| 158 | coding tools, background-shell lifecycle tools, and the stable capability proxy: |
| 159 | |
| 160 | `bash` on POSIX or `pwsh` on Windows, `job_output`, `job_kill`, `edit_file`, |
| 161 | `read_file`, `view_image`, `write_file`, `compress` (when registered), and |
| 162 | `use_capability`. |
| 163 | |
| 164 | Optional tools (`glob`, `grep`, `ls`, `web_fetch`, MCP, skills, subagents, docs, |
| 165 | session history, memory mutation, workflow, and so on) remain in the host |
| 166 | registry for dispatch. The model lists, inspects, calls, or declines them via |
| 167 | `use_capability` without changing the provider tool list. Task risk changes host |
| 168 | planning, verification, and review policy, not which tools appear on the |
| 169 | provider-visible surface. The retired `connect_tool_source` path is no longer registered. |
| 170 | |
| 171 | ## Invalid arguments and recovery |
| 172 | |
| 173 | The host validates concrete tool arguments before extension interception, |
| 174 | permission prompts, hooks, write leases, subagent execution, or tool dispatch. |
| 175 | An extension replacement is resolved and validated again. Invalid arguments are |
| 176 | an unexecuted tool error, not a permission refusal: correcting the input can |
| 177 | succeed on any subsequent call without an inspect action or a new user turn. |
| 178 | Normal permission and execution checks still apply to a corrected call. |
| 179 | |
| 180 | Errors retain the target name, schema fingerprint, violation paths, and |
| 181 | `argument_validation:<tool>:<fingerprint>:<category>` diagnostic signature. |
| 182 | Feedback identifies whether parameters belong at the direct tool's input root |
| 183 | or inside a capability call's `arguments`. A conservative, value-free hint may |
| 184 | identify a single redundant `arguments` wrapper when the inner object satisfies |
| 185 | the concrete contract, including conditional validation. This is advice only: |
| 186 | the host never unwraps, coerces, fills, or executes the supplied parameters as |
| 187 | part of diagnosis. Legitimate `arguments` fields and nested skill contracts are |
| 188 | preserved. Empty/null validation compatibility remains unchanged. |
| 189 | |
| 190 | Input errors returned before capability resolution also receive contract |
| 191 | feedback when the outer schema establishes the error. Successful resolution is |
| 192 | not subjected to a new envelope gate; unavailable targets and authorization |
| 193 | errors keep their own reasons. A malformed host schema is a configuration |
| 194 | problem, not something the model can fix by rewriting arguments. Existing |
| 195 | third-party MCP schema-compilation fallback remains unchanged. |
| 196 | |
| 197 | `inspect` remains a contract discovery operation, not an unlock requirement. |
| 198 | There is no schema-specific error counter or third-failure tool lock. The shared |
| 199 | storm breaker gives a soft convergence hint after three consecutive equivalent |
| 200 | failed batches; multiple calls in one batch do not add multiple rounds, and a |
| 201 | successful result resets the existing failure streak. Parameter-only failures |
| 202 | receive correction advice rather than instructions about bypassing permissions. |
| 203 | If correction remains unsuccessful, the model may report tool argument |
| 204 | generation failure and unfinished work. This does not mark the work completed. |
| 205 | Real permission, Plan-mode, hook, and write-loop restrictions remain enforced. |
| 206 | |
| 207 | Convergence is advisory. With `MaxSteps=0` and no explicit budget, there is no |
| 208 | fixed-round hard stop; configured step/spend limits and cancellation still work. |
| 209 | No extra repair-model request, provider-specific switch, or tool-schema change |
| 210 | is introduced. Feedback is bounded to 4 KiB and appended to the failed tool |
| 211 | result without rewriting prior messages or the stable provider prefix. Additional |
| 212 | feedback consumes context tokens; historical error messages are left intact. |
| 213 | |
| 214 | Argument validation/failure/skip/remote-dispatch counters retain their meaning; |
| 215 | internal wrapper checks do not count as additional calls. The legacy |
| 216 | `capability_loop_guard.RepeatFailures` and `BlockedCalls` fields remain in metrics |
| 217 | for compatibility but are no longer incremented by new runs. They are not |
| 218 | repurposed as storm-intervention counters; existing `loop_guard` notices describe |
| 219 | those interventions. No session/config migration is required. Downgrading restores |
| 220 | the older error-recovery behavior without changing the stored conversation. |
| 221 |