magi-code 0.89.0

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

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

Choose a tool below, then check its path, output, and credential limits before use. Shell guardrails are not an OS sandbox.

| Tool | Contract |
| --- | --- |
| `read` | Read known local files or internal resources. Local output supplies tags and line anchors for `hash_edit`; see [reading files](#reading-files). |
| `bash` / `shell` | Run non-interactive commands in the active cwd with configurable preflight, timeout, and bounded output. The host shell has no OS sandbox; see [environment profiles](security.md#subprocess-environment-boundaries). |
| `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 anchored changes to existing files. |
| `write` | Create or fully replace a file; both `path` and `content` are required. Empty content is allowed, replacement is atomic, parent directories are created recursively, UTF-8 content is capped at 1 MiB, and final symlink targets are rejected. Relative paths resolve from cwd; absolute behavior follows `capabilities.tools.write.absolute_paths`. Use for new files or complete replacements. |
| `grep` | Bounded, in-process content search with OR patterns, ranked/raw modes, pagination, and partial-scan metadata. No external `rg` is needed. |
| `find` | Fuzzy path discovery with pagination and Git/Perforce ignore rules; directory listings are cached, not file contents. |
| `list_files` | Return raw shallow direct child files/directories; does not read or honor ignore files. `find` and `grep` use shared git/P4-aware traversal where applicable. |
| `ast_grep` | Embedded AST search, read-only rewrite previews, and outlines; no external CLI needed. Directory requests use shared Git/Perforce-aware walking of visible regular files. Explicit file requests bypass ignore filters but retain path checks. Empty visible worksets return successful no-match. |
| `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 `capabilities.tools.view_image.vision_model` (`openai-codex`/`openai` Responses API, Anthropic Messages API, or OpenAI-compatible custom provider). Never falls back to active chat model. |
| `subagents` | Run independent delegated tasks in bounded parallel child sessions, optionally selecting a discovered subagent `identity`. |
| `web` | Public research and programming documentation. `search` accepts a query; `open` accepts a public URL or cached reference. See below. |

## Shell commands

`bash` requires `command` and a nonblank `intent` that briefly explains why the command is needed. The transcript card shows intent above command, then result summary. `timeout` is optional (1–300 seconds, default 30). Runtime aliases `shell` and `cmd` also require intent.


## AST search and outlines

`ast_grep` defaults to `operation: "search"`. Search requires `pattern` and accepts optional `rewrite` for a read-only diff preview. Use `operation: "outline"` to inspect symbols before reading source.

Neither operation needs an installed CLI. Embedded AST libraries infer language from file extension unless you supply one. Project configs and custom grammars are not loaded.

- Search returns `path:line:text` with 1-based lines. Rewrite previews identify each match by path, line, and exact end-exclusive byte range, then show removed `-` lines and replacement `+` lines. Surrounding text remains unchanged, and files are never written. Overlapping matches produce independent previews, not one combined patch.
- `limit` bounds returned lines (default 100, maximum 500); output is capped at 64 KiB. A boundary can cut a preview short, so check `truncated` before using it.
- Pattern and rewrite each accept up to 4 KiB. Search accepts complete UTF-8 files up to 256 KiB, visits at most 10,000 files, and reads at most 128 MiB per call. Syntax trees are limited to 16,384 nodes and depth 64; patterns to 256 nodes. Replacement allocation estimates are capped at 1 MiB before expansion. `files_skipped`, `partial`, and `truncated` report oversized, unreadable, non-UTF-8, or overly complex files.
- Parsing, including pattern parsing, checks cancellation and a shared 30-second deadline. Traversal checks each AST node before matching. A single bounded library match/replacement or filesystem operation cannot be interrupted mid-call. No detached workers are created. Unknown file types are skipped. In mixed-language searches, a pattern that cannot compile for one language makes results partial. Supply a language for strict pattern validation.
- Outline shares search's complete-file, file-count, scan-byte, and syntax-tree limits. Bundled rules cover Rust, TypeScript/TSX, JavaScript, Python, Go, Kotlin, Java, and Swift. Other languages are ignored.

```json
{"operation":"outline","path":"src","language":"rust","limit":30}
```

- For outline, omit `pattern` and `rewrite` or set them to JSON `null`. Other values, including empty strings and placeholders, are rejected. Outline-only fields are `items` (`structure` by default, or `exports`, `imports`, `all`), `view` (`digest` by default, or `names`, `signatures`, `expanded`), `name` (Rust regex over top-level name/signature/first source line), `symbol_type` (comma-separated lower camel case types), and `pub_members` (default false). For search, omit these fields or set them to `null`; `pub_members: false` does not count as absent. Filters do not select members directly. Name/type filters are capped at 512 bytes.
- All fields except `operation` treat `null` as omission. Search still requires a nonempty `pattern`. If supplied, `operation` must be `search` or `outline`. Providers that require every key can use `"pattern":null,"rewrite":null` for outline and null outline-only fields for search. Invalid mode-specific arguments report offending fields with an omit-or-null example.
- Explicit `structure`/`digest` defaults retain internal symbols for directory and file requests. `names` omits members and computes only names, though name filtering still computes signatures. `signatures` includes top-level signatures; `digest` adds member names; `expanded` adds member signatures.
- Each output line is a complete JSON object with `path`, `language`, and `item`. Items contain names, types, signatures, export/import flags, ranges, and direct members selected by the view. Lines and columns are 1-based; range ends are exclusive. Byte offsets are omitted.
- `limit` counts top-level entries, not members or source lines (default 100, maximum 500). A parent and its selected members are kept or omitted together. Returned entry text is capped at 64 KiB.
- Outline checks cancellation and the shared 30-second deadline before and after each extraction iterator step and before accepting output. One step may traverse the remaining bounded file tree and collect direct members. Individual library calls, including compilation of fixed bundled rules, and filesystem operations cannot stop mid-call. The deadline is cooperative, not a strict timeout. No child processes or detached workers are created.
- Check `symbols_returned`, `files_skipped`, `partial`, and `truncated`. Byte and entry limits retain only complete earlier entries; deadlines return partial failure. Empty outlines succeed. Project or ancestor `sgconfig.yml`, custom language libraries, and custom outline rules are never loaded. Only approved regular files are read under the same path and read checks as search.


## URL routing

Use the narrowest tool that meets the request:

| Need | Tool | Use |
| --- | --- | --- |
| Public unauthenticated plain text, Markdown, JSON, or HTML ordinary GET | `web` open | Bounded native fetch/readability; no JavaScript or interaction. |
| Public research and page text | `web` | Search and open; no browser interaction or CSS extraction. |
| Custom or non-public HTTP work | `bash` | Custom methods, headers, bodies, redirect control, binary downloads, private/local services, or an explicit request. |

URL open applies bounded readability extraction to substantial HTML and converts short HTML to Markdown. Never block or rewrite `curl`.

## `web` contract

Use one operation per call. Omit unused fields or set them to null:

```json
{"operation":"search","query":"Rust official release notes","limit":5,"domainFilter":["rust-lang.org","-old.rust-lang.org"],"recencyFilter":"month"}
{"operation":"open","url":"https://www.rust-lang.org/"}
{"operation":"open","reference":"web:<reference from a result>"}
```

- With active `openai-codex`, search uses the active model and existing Codex OAuth/refresh at `https://chatgpt.com/backend-api/codex/responses` with native `web_search`. Other providers use Exa `/search` and require environment-only `EXA_API_KEY`. URL open fetches directly without credentials or an Exa key. Search backends do not fall back to each other or MCP.
- Codex returns a labeled model-generated synthesis with URL citations, not page extracts. It creates no cached references; open cited URLs separately for page text. `limit` requests a source count but does not enforce it. Codex rejects `domainFilter` and `recencyFilter`; omit both. The filtered example above applies only to Exa.
- Codex search requires `response.completed`, rejects failed or incomplete streams, and caps streams at 2 MiB. It reuses the provider's blocking transport, cancellation, byte-idle and semantic-progress watchdogs, and session-expiry refresh. Tool output remains capped at 48 KiB.
- Query: 1 to 512 characters after trimming. URL: at most 2,048 UTF-8 bytes. Search results: 1 to 10, default 5.
- Search-only `domainFilter` accepts at most 20 bare ASCII domains (253 characters per domain, 63 per label). Prefix `-` to exclude. Schemes, ports, paths, whitespace, empty labels, and leading/trailing label hyphens are rejected. Entries are trimmed. Includes/excludes map to Exa `includeDomains`/`excludeDomains`. `recencyFilter` accepts `day`, `week`, `month`, or `year`, mapped to `startPublishedDate` at the current UTC date minus 1, 7, 30, or 365 days. It filters publication date, not crawl freshness. For open, omit filters or set them to null. Aliases and query batches are unsupported.
- Only public HTTP(S) URLs are accepted. Credentials and sensitive URL parameters are rejected. URL open uses the DNS-pinned transport in `url_fetch.rs`, which rejects private/local destinations and redirects. Exa result URLs are also validated; Exa controls retrieval of search extracts.
- Exa search text requests are capped at 10,000 characters per page and responses at 2 MiB, with 5s/20s connect/request timeouts. Direct URL open downloads at most 512 KiB with 5s/15s connect/request timeouts and a 5,000-token text budget. Both check cancellation, cache at most 40,000 bytes per page, and cap output at 48 KiB with truncation metadata.
- Exa search shows source URLs, stable references, and bounded excerpts. Cached open returns stored text.
- Runtime stores up to 64 immutable page snapshots and evicts the oldest. Runtime clones share this cache. Sessions do not persist or restore it. An expired or unknown reference fails without network work; reopen the public URL for a new reference.
- URL open makes a direct GET and returns readable text, which may omit page content. Cached open returns the same snapshot without refreshing. Output gives no publication, crawl, or fetch timestamps; do not infer them from a reference.

## Visible tool output

Mission Control shows pending and completed tool activity with safe status, metadata, and bounded output. Pending `write`/`hash_edit` entries do not include file contents or replacement payloads. `web` shows query/result/truncation metadata and redacts credential-shaped text. `read` shows requested paths. Subagent summaries omit raw child output. Shared summaries live in `src/output/tool_summary.rs`.

When opt-in LSP edit injection has fresh diagnostics, they are appended to the successful `write`/`hash_edit` tool result. Mission Control may show successful `hash_edit` changes as a bounded changed-line transcript preview and a larger activity-detail diff. Both are display-only, not provider tool results or JSONL session data.

`subagents` displays a bounded aggregate summary rather than raw child output.

## Reading files

Use `paths` for known local files or skills:

```json
{ "paths": ["README.md:1-200"] }
```


- Provider-facing schema requires `paths`, accepting 1 to 8 entries. Runtime validation rejects missing, empty, blank, or unknown-file inputs.
- Recognized read targets: local files, `skill://<name>` skill definitions, and `skill://<name>/<relative-reference>` skill reference files. Other resource schemes are unsupported. HTTP(S) targets fail with guidance to use `web` open.
- String selector suffixes: `:N`, `:-M`, `:N-M`, `:N+`, `:N+K`, `:raw`, and compounds such as `:raw:50-100`. Selectors apply independently to each target.
- Each local file remains limited to 1 MiB and UTF-8 text. Local files without a selector return up to 400 lines; use line selectors to choose another range.
- 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 `find` for fuzzy path discovery or `grep` 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 byte caps or path/skill 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.


## Finding paths

Discover paths without reading 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. Relative paths stay within cwd; absolute paths outside cwd require `capabilities.tools.find.absolute_paths` (default `true`). Output is slash-normalized, cwd-relative for paths within cwd and absolute for outside paths.
- `find` is read-only, uses the shared workspace walker, and does not replace `grep` content search.
- Each scan cooperatively checks cancellation, visits at most 100,000 entries (including the root), and has a 10-second deadline. Filesystem calls can exceed a cooperative deadline. Narrow `path` when a scan is partial.
- `scan_complete` and `total_matched_exact` identify exhaustive scans. Partial scans rank only observed matches, set `truncated: true` and `index_ready: false`, and report `has_more: null`; `total_matched` is then a lower bound. `entries_scanned`, `entry_limit_reached`, `timed_out`, `walk_errors`, and `walk_entries_omitted` explain incomplete traversal. Paging does not resume a partial scan.
- Workspace walking honors standard hidden/`.ignore`/Git filters plus Perforce defaults and configuration. It prunes ignored directories before scanning and feeds `find`, `grep`, and directory-mode `ast_grep`; `list_files` intentionally remains a raw shallow listing.
- Perforce resolution follows `P4CONFIG` → `P4ENVIRO` → process environment → Windows user/system registry. `P4IGNORE` accepts ordered semicolon-separated relative or absolute rule files; unset values use `.p4ignore` followed by `p4ignore.txt`. Non-macOS POSIX systems default `P4ENVIRO` to `$HOME/.p4enviro`; Windows and macOS require it explicitly. `.p4root` is always excluded; `.p4config` and configured `P4CONFIG` filename are excluded when configuration discovery is active. P4 rule/config reads are bounded. macOS `p4 set` preferences are not read directly; expose those values through `P4CONFIG`, `P4ENVIRO`, or process environment.
- `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.

## Searching contents

```json
{ "patterns": ["fn run"], "path": "src", "limit": 20, "context": 2 }
```

- `grep` is fully in-process Rust search; it does not spawn `rg` or require search credentials.
- `patterns` is required, accepts 1 to 8 exact strings, and uses OR semantics.
- Each pattern is matched as a case-sensitive regex after common brace/parenthesis repairs; failed compilation falls back to literal matching. The original pattern bytes are preserved for matching diagnostics and display.
- Ranked mode is the default: it emits `grouped_v1` path headers, `[def-like]`, `[match]`, and `[import]` primary labels, and deduplicated context rows. Raw mode emits flat deterministic `path:line:content` rows and uses one extra match as a pagination lookahead.
- `limit` and `offset` apply to primary matching lines; `context` adds at most 20 lines on either side and does not consume the primary limit. Ranked mode keeps at most five primary hits per file and reports omitted hits with `truncated` and `per_file` metadata.
- Reads are capped at 4 MiB per file and 128 MiB per invocation; traversal is capped at 10,000 files and 10 seconds. The provider-visible output is capped at 65,536 UTF-8 bytes and records are added atomically.
- Hidden, ignored, `.git`, `target`, binary, special, and unreadable files are skipped as applicable. Metadata reports scan byte/file limits, per-file limits, recovery skips, binary/invalid-UTF-8 counts, matcher adjustments/fallbacks, exact-match status, and truncation reasons.

## Editing files

Anchor patches to the latest 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.

## Inspecting images

Configure an explicit vision provider/model before use:

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

Rules:

- `path` must be an absolute path to one existing regular local image. The path is canonicalized; supported formats are PNG, JPEG, GIF, and WebP, with case-insensitive extensions that must match the file signature. Outside-cwd access follows `capabilities.tools.view_image.absolute_paths`.
- `prompt` is required and should name the exact visual details to inspect.
- Image bytes go only to the explicitly configured vision model; there is no active-chat-model fallback.
- `capabilities.tools.view_image.max_image_bytes` defaults to 5 MiB and is configurable from 1 byte through 20 MiB. Returned text is capped at 64 KiB.


LSP edit diagnostics are opt-in and workspace-bound. Missing, slow, crashed, stale, or indexing servers skip injection without changing `write` or `hash_edit` success. They complement project checks and 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 resolve from the active working directory and cannot escape it. Built-ins accepting caller-controlled OS paths have independent, default-allow absolute-path settings; see the authoritative [Configuration](configuration.md) matrix for keys, aliases, exceptions, and opt-out behavior. LSP edit-diagnostic injection remains workspace-bound because it has no caller path argument.
- `bash` / `shell` allows shell expansion by default. Set `capabilities.tools.bash.shell_expansion: false` to reject `$`, `~`, backticks, `{`, and `}` in bash commands during preflight.
- `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. Write replacement is atomic, but committed-but-undurable failures are possible.
- 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.
- `web` uses Codex OAuth for active Codex search and environment-only `EXA_API_KEY` for other search, with no backend/MCP fallback. URL open fetches directly without an API key. URL checks, timeouts, bounded snapshots, and output limits are described [above](#web-contract).
- `grep` output is capped at 64 KiB; `web` output is capped at 48 KiB with explicit truncation metadata.
- Mission Control has separate [transcript-card and Selected Activity Detail projections](mission-control-tui.md); `subagents` shows only safe summaries. Local/session raw results remain unchanged by these display projections. Do not ask tools to read or print secrets unless you accept them 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 `agent.reminders.enabled` is `true`, rules inspect assistant streaming deltas and completed tool-call arguments mid-turn. Regex rules stay dormant until a match; on a match, magi-code aborts the current stream, records a 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 `agent.reminders.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.

## Related docs

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