magi-code 0.61.0

Repository-aware CLI coding agent for terminal work
Documentation
# Tools and safety model

[Feature docs index](README.md) · [Repository README](../../README.md)

## Purpose

Review built-in tool contracts, visibility, path boundaries, mutation limits, search credentials, and shell-safety expectations.

## Details

Built-in tool contracts:

| Tool | Contract |
| --- | --- |
| `read` | Read 1 to 8 scoped UTF-8 local files or internal resources with `paths`, each up to 1 MiB, with optional 1-based line offset and maximum line limit of 2,000. Local file output defaults to hashline mode (`[path#TAG]` plus `LINE:TEXT`) for `hash_edit`; selector suffixes support line windows and `:raw`. `skill://<name>`, `skill://<name>/<relative-reference>`, `session://`, `issue://`, `pr://`, and direct `http`/`https` URLs route through bounded internal readers. Output is aggregate-capped at 1 MiB including file headers and errors. |
| `bash` / `shell` | Run a non-interactive command in the active working directory with configurable cwd preflight, timeout, and bounded captured output. Unix-like hosts use `/bin/bash -lc`; native Windows hosts use PowerShell. |
| `browser` | Control a browser through external `dev-browser` CLI with strict commands, serialized session state, and bounded stdout/stderr. Supports headless/headed managed Chromium and connect mode. |
| `hash_edit` | Apply hashline patches to existing UTF-8 files using latest current-session `read` tags, line anchors, stale-tag recovery, optional block ops, whole-file `MV`, and regular-file-only `REM`. Use for existing-file edits. |
| `write` | Create/overwrite a file and create parent directories as needed, with content limited to 1 MiB. |
| `ffgrep` | Search file contents with fully in-process Rust engine using `grep-regex`, `grep-searcher`, and `ignore`; no external `rg` process. Chooses parallel, sequential, or windowed search strategy by limit/workset, rejects FIFOs/sockets/devices, bounds output, sanitizes common regex mistakes, and falls back to literal matching when needed. |
| `fffind` | Search cwd-relative file and directory paths through the shared gitignore-aware workspace walker, backed by listings-only `fs_cache`, fuzzy matching, pagination, and bounded output. |
| `list_files` | List direct child files/directories through the same workspace/listing cache path used by `fffind`, `ffgrep`, and `repo_map`; never reads file contents. |
| `repo_map` | Return compact ranked structural map of Rust/Python/TypeScript/TSX/Go symbols. Accepts optional `path`, `query`, and `maxTokens` 512–20,000 (default 4,096). Respects `.gitignore`, skips generated/vendor dirs, caps files/bytes/symbols/references, mutates nothing, and ranking is approximate. |
| `diagnostics` | Return latest cached LSP diagnostics for one safe file path or a bounded workspace summary. Available when LSP is enabled; output is capped, redacted, and not a replacement for project checks. |
| `references` | Query LSP references for a safe file path and 1-based line/column position, returning bounded `path:line:column` locations. Use for dead-code verification where text search can match comments or strings. |
| `view_image` | Inspect one local image through a configured vision model. Requires absolute `path` and `prompt`; supports `png`, `jpg`, `jpeg`, `gif`, and `webp`; enforces `max_image_bytes`; sends image bytes only to configured `tools.view_image.vision_model` (`openai-codex`/`openai` Responses API, Anthropic, Claude Code direct Anthropic Messages API, or OpenAI-compatible custom provider). Never falls back to active chat model. |
| `parallel_subagents` | Run independent delegated tasks in bounded parallel child sessions, optionally selecting a discovered subagent `identity`. |
| `web_search` | Search current public web information through EXA REST `/search`. Requires `queries` with 1–5 strings; use one item for a single search. Also accepts `numResults` 1–10 (default 5), `includeContent` (default false), `recencyFilter` `day`/`week`/`month`/`year`, and up to 20 bare-domain include/exclude filters (`-domain.com` excludes). Requires `EXA_API_KEY`. |
| `code_search` | Search programming/API docs, examples, GitHub discussions, and code-oriented sources through EXA REST `/search`. Accepts `query` and `maxTokens` 1,000–10,000 (default 5,000); query is enriched with official documentation/code examples/GitHub terms and output is capped. Requires `EXA_API_KEY`. |

Visible tool-call blocks remain plain in `--print` and trailing-prompt modes and use concise `STATUS`, metadata, and optional `OUTPUT` lines without `--- TOOL START/END` wrappers. Mutating `write` calls appear as soon as they start with `STATUS: writing`, safe path/size metadata, and then resolve with the final success/failure block. Mutating `hash_edit` calls appear as soon as they start with `STATUS: running`, safe input-byte metadata, and then resolve with the final success/failure block. When opt-in LSP edit injection has fresh diagnostics, the diagnostics text is appended to the same successful `write`/`hash_edit` tool result. `browser` calls appear while running and show safe command/mode/action/exit/truncation metadata without generated JavaScript or binary screenshot data. `read` calls show requested path values instead of path counts. `repo_map` calls show path/query/files/symbol/truncation metadata without duplicating symbol lists in summary metadata. `diagnostics` and `references` show safe path/server/count/truncation metadata without raw language-server streams. `web_search` and `code_search` calls show query/result/truncation metadata and redact EXA credential-shaped text. `parallel_subagents` calls show safe aggregate summary output and never raw child output. Pending `write`/`hash_edit` entries do not include file contents or edit replacement payloads. Mission Control activity detail may additionally show successful `hash_edit` changes as display-only diffs; those diffs are not added to provider tool results, JSONL sessions, or classic plain tool blocks.

```text
STATUS: success
PATHS: README.md
OUTPUT:
<tool output>
```

`parallel_subagents` uses the same wrapper-free shape but displays only a bounded aggregate summary:

```text
STATUS: success
OUTPUT:
summary: total=2 completed=2 failed=0
```

`read` accepts a bounded list of known file paths/resources:

```json
{ "paths": ["README.md"], "offset": 1, "limit": 2000 }
```

```json
{ "paths": ["README.md", "docs/features/tools-and-safety.md"], "offset": 1, "limit": 2000 }
```

Rules:

- Provider-facing schema requires `paths`, accepting 1 to 8 entries; runtime validation rejects missing, empty, blank, unknown-file, or both `paths` and legacy `path` inputs.
- Legacy single-file `path` remains accepted only by runtime/session replay; new tool calls should use `paths`.
- Recognized read targets: local files, `skill://<name>` skill definitions, `skill://<name>/<relative-reference>` skill reference files, `session://<id>` session records, `issue://<n>` GitHub issues via `gh`, `pr://<n>` GitHub PRs via `gh`, and direct `http`/`https` URLs through the bounded URL-fetch path.
- String selector suffixes: `:N`, `:N-M`, `:N+`, `:N+K`, `:raw`, and compounds such as `:raw:50-100`. Selectors apply to the single target string they suffix; global `offset`/`limit` still apply uniformly when provided.
- Each local file remains limited to 1 MiB and UTF-8 text. `offset` and `limit` apply uniformly to every requested path; `limit` is at most 2,000 lines per file.
- Multi-file output uses `--- FILE: <requested path> ---` sections. File-specific errors appear as `ERROR:` sections, and successful file sections remain visible when another requested file fails.
- Complete multi-file `ToolResult.content` is capped at 1 MiB measured as UTF-8 bytes, including file headers, separators, file content, and `ERROR:` text.
- `read` does not search, expand globs, or discover files. Use `fffind` for fuzzy path discovery or `ffgrep` for content search, then `read` known paths/resources.
- Local file reads default to hashline output: `[path#TAG]` header plus 1-indexed `LINE:TEXT` rows.
- `:raw` bypasses hashline formatting for plain text (`path:raw`, `path:raw:20-40`); it does not bypass file caps or URL/resource protections.
- Skill reference reads are limited to discovered/enabled skills. References must be relative, cannot contain `..`, cannot target symlinks or directories, must remain under the canonical skill directory, and must be UTF-8 under the 1 MiB read cap.
- `hash_edit` anchors must use the newest tag and visible line numbers from the most recent `read` or successful `hash_edit` response for that file. Stale-tag failures mean re-read first, then retry with fresh anchors.

`browser` controls a session-scoped page through `dev-browser`:

```sh
npm install -g dev-browser
dev-browser install
```

```json
{ "command": "open https://example.com" }
```

```json
{ "command": "snapshot -i" }
```

```json
{ "command": "click @e12" }
```

```json
{ "command": "screenshot --full" }
```

```json
{ "command": "close" }
```

Schema:

```json
{
  "command": "open https://example.com",
  "mode": "headless",
  "connectUrl": "http://localhost:9222",
  "ignoreHttpsErrors": true
}
```

Rules:

- `command` is required. `mode` defaults to `headless` and accepts `headless`, `headed`, or `connect`.
- `connectUrl` is valid only with `mode: "connect"`; `ignoreHttpsErrors` is valid only with managed `headless`/`headed` modes.
- Accepted command grammar: `open <url>`, `snapshot [-i]`, `click <@ref>`, `fill <@ref> <text>`, `type <@ref> <text>`, `select <@ref> <value>`, `press <key>`, `scroll <up|down|left|right> [px]`, `get title|url|text [@ref]`, `wait <@ref|ms>`, `screenshot [--full]`, `close`.
- Refs accept `@e12` or `e12`; snapshots rewrite `[ref=e12]` to `@e12` for follow-up commands.
- Browser calls are serialized, use page name `main`, run `dev-browser` directly without shell, and never call global `dev-browser stop`.
- Non-zero `dev-browser` exits return recoverable tool failure content. Missing CLI reports install instructions. Timeout, cancellation, and output caps terminate the child process tree.
- Screenshots return `Screenshot saved: <path>` text only; magi-code does not inline image bytes in this first pass.

Transport examples:

```json
{ "command": "open https://localhost:3000", "mode": "headed", "ignoreHttpsErrors": true }
```

```json
{ "command": "snapshot -i", "mode": "connect", "connectUrl": "http://localhost:9222" }
```

`fffind` discovers paths without reading file contents:

```json
{ "query": "lib", "kind": "mixed", "path": "src", "limit": 20, "offset": 0 }
```

Rules:

- `query` is required, trimmed, and limited to 512 characters.
- `kind` defaults to `files`; `directories` results end with `/` in output, and `mixed` can return both files and directories.
- `limit` defaults to 50 and is capped at 200; `offset` supports pagination.
- `path` is optional and must resolve to an existing directory under cwd. Output stays cwd-relative and slash-normalized.
- `fffind` is read-only, uses the shared workspace walker, and does not replace `ffgrep` content search.
- Workspace walking is gitignore-aware, performs single-pass traversal for shared consumers, discovers applicable `AGENTS.md`, and feeds `fffind`, `ffgrep`, `repo_map`, and `list_files`.
- `fs_cache` stores directory listings only, keyed by path plus directory mtime with TTL expiry. It does not cache file bytes or provider-visible tool output.

`ffgrep` searches file contents:

```json
{ "pattern": "fn run", "path": "src", "limit": 20, "context": 2 }
```

- `ffgrep` is fully in-process Rust search. It does not spawn `rg`.
- Engine uses `grep-regex` + `grep-searcher` + `ignore`; path traversal shares the workspace walker and gitignore behavior with `fffind`/`repo_map`/`list_files`.
- Strategy selection: parallel for no/small limits, sequential for `limit <= 64`, and 512-file windowed search for larger bounded worksets.
- File reading uses an in-memory `Vec` for small files under 128 KiB, memory maps large files, and prefix-only reads for oversized files over 4 MiB.
- Regex input is sanitized for common brace and unescaped-parenthesis mistakes (`sanitize_braces`, `escape_unescaped_parentheses`); if regex compilation/search still fails, literal fallback is used.
- FIFOs, sockets, and device files are rejected.
- Output remains newline-separated `path:line:content`, with cwd-relative slash-normalized paths when results are under runtime cwd, a hard visible `limit` after context expansion, and a 64 KiB provider-visible output byte cap.
- Result ordering depends on selected in-process strategy and should not be treated as stable API.
- Metadata includes `engine`; success reports the Rust in-process engine.


`hash_edit` applies hashline patches anchored to read tags:

```json
{ "input": "[README.md#A1B2]\nSWAP 10.=10:\n+new line" }
```

Rules:

- Each file section starts `[PATH#TAG]`; `TAG` must come from latest current-session hashline `read` output or previous successful `hash_edit` response.
- Supported ops: `SWAP`, `SWAP.BLK`, `DEL`, `DEL.BLK`, `INS.PRE`, `INS.POST`, `INS.BLK.POST`, `INS.HEAD`, `INS.TAIL`, `MV`, and `REM`.
- Body-taking ops end with `:` and accept only `+TEXT` rows. Literal leading plus/minus content still needs the body prefix: `++value`, `+- item`.
- Stale tag or surprising result: re-read file, then reissue patch with fresh tag/line anchors.
- `hash_edit` does not create new files; use `write` for creation.

`view_image` uses explicit vision settings; configure a provider/model before use:

```json
{
  "tools": {
    "view_image": {
      "vision_model": {
        "provider": "local-vision",
        "model": "vision-model-id"
      },
      "absolute_paths": true,
      "max_image_bytes": 5242880
    }
  }
}
```

Rules:

- `path` must be absolute and end in `png`, `jpg`, `jpeg`, `gif`, or `webp`.
- `prompt` is required and should name exact visual details to inspect.
- Image bytes go to configured vision model only; active chat model is not reused as fallback.
- `tools.view_image.max_image_bytes` bounds payload size.


`diagnostics` and `references` use opt-in LSP state:

```json
{ "path": "src/example.rs", "limit": 50 }
```

```json
{ "path": "src/example.rs", "line": 42, "column": 13, "include_declaration": false, "limit": 50 }
```

Rules:

- LSP is disabled by default; enable `lsp.enabled` and install matching language servers on `PATH` before expecting useful output.
- `diagnostics` accepts optional `path`; with no path it returns a bounded workspace summary from cached diagnostics.
- `references` requires an existing safe file path plus 1-based `line` and `column`; `include_declaration` defaults to `false`.
- Both tools validate paths through existing cwd/root boundaries before querying a server.
- Outputs are redacted and capped. Raw LSP stdout/stderr is never returned in tool content, metadata, sessions, or provider context.
- LSP failures never make `write` or `hash_edit` fail; missing, slow, crashed, stale, or indexing servers skip edit injection or return concise query guidance.
- LSP diagnostics complement project checks. They do not replace `cargo check`, tests, linters, or repository-specific verification.

Safety boundaries:

- Tool calls and effects are represented in the session transcript.
- Relative paths are resolved from the active working directory and cannot escape it. `read`, `hash_edit`, `write`, `ffgrep`, `fffind`, `bash`, and `parallel_subagents` allow absolute paths by default; each can opt out with `tools.<tool>.absolute_paths: false` to require cwd-bounded absolute-path behavior.
- `bash` / `shell` allows shell expansion by default. Set `tools.bash.shell_expansion: false` to reject `$`, `~`, backticks, `{`, and `}` in bash commands during preflight.
- `read`, `ffgrep`, and `fffind` are read-only.
- `hash_edit` and `write` reject final symlink mutation targets and enforce size/count limits. `hash_edit` validates all sections before mutation when semantic/path checks fail, rejects unanchored or unseen-line hunks, and guards `REM` to regular files only.
- Cwd/root checks reduce accidental escapes and common symlink escapes, but they are not a full TOCTOU-safe sandbox against a malicious concurrent local process racing the filesystem.
- `bash` / `shell` runs through the host shell and is not an OS sandbox.
- `read skill://<name>` resolves names only against the already-discovered skill list; absolute paths, traversal, directories, symlink escapes, non-UTF-8 files, and files over 1 MiB are rejected.
- `browser` uses `Command::new("dev-browser")` directly, never a shell. Provider text is parsed by strict grammar and every generated JavaScript string literal is JSON-quoted. Temporary scripts live in a unique OS temp directory and are removed after each call. Browser sessions are named per `ToolRuntime`, cloned runtimes share serialized state, and `close` only closes page `main` for that named session.
- `web_search` and `code_search` use direct EXA REST only, require `EXA_API_KEY` in the process environment, do not use `MC_API_KEY`/`OPENAI_API_KEY`, do not read EXA keys from settings/auth files, and have no MCP or `fetch_content` fallback.
- Search and browser outputs are bounded: `ffgrep` content output is capped at 64 KiB, browser stdout is capped at 64 KiB, browser stderr at 16 KiB with a 60s timeout, EXA connect timeout 5s, request timeout 20s, web inline content 2,000 chars/source and 8,000 chars/query, code context up to `maxTokens * 4` chars (clamped 4,000–40,000), and EXA provider-visible tool output 48 KiB with explicit `[truncated]` markers.
- Visible tool output generally follows tool result content, except `parallel_subagents` display only safe aggregate summaries, and opt-in provider-visible tool output compression can summarize recognized `bash` outputs only for the next provider request. Local/session raw `ToolResult` preservation remains unchanged. Do not ask tools to read or print secrets unless you accept those secrets appearing in terminal output and session artifacts.
- File mutations are coordinated by per-file mutation locks.

## Token-triggered streaming reminders

TTSR is disabled by default. When `ttsr.enabled` is `true`, rules inspect assistant streaming deltas and completed tool-call arguments mid-turn. Regex rules stay dormant until a match; on match, magi-code aborts the current stream, records hidden local `ttsr_injection` JSONL event, injects the configured system reminder, and retries the turn.

Built-in rules cover destructive commands, secret exfiltration, credential routing, cwd widening, and force push patterns when TTSR is enabled. User rules live in `ttsr.rules` settings as `{ "pattern": "...", "reminder": "..." }` objects. TTSR is separate from hooks: hooks run at phase boundaries around tool/assistant events, while TTSR can interrupt provider streaming before a tool executes or assistant text completes.

## Provider-visible tool output compression

`tools.output_compression.enabled` defaults to `false`. When enabled, recognized `bash` commands send deterministic curated summaries to the provider while local display and JSONL session tool results keep raw output. Current rules cover `git status`, `git diff`, `cargo check`, and `cargo test`; non-`bash`, unrecognized, and compound shell commands pass through unchanged. See [Tool output compression](tool-output-compression.md) for rule format, examples, and extension guidance.

## Related docs

- [Tool output compression](tool-output-compression.md)
- [Tool-call bash hooks](tool-call-hooks.md)
- [Parallel subagents](parallel-subagents.md)
- [LSP diagnostics](lsp-diagnostics.md)

---

[Back to feature docs](README.md) · [Back to repository README](../../README.md)