magi-code 0.77.1

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, internal resources, or public URLs. 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 through Exa. `search` accepts a query; `open` accepts a public URL or cached reference; `find` searches cached text without refetching. See below. |

## AST search and outlines

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

Search and outline need no installed CLI. They use pinned `ast-grep-core`, `ast-grep-language`, and `ast-grep-outline` 0.44.0 (declared Rust 1.85 minimum, tree-sitter 0.26). Language is inferred from the extension unless supplied explicitly; project configs and custom grammars are not loaded.

- Search returns `path:line:text` with 1-based lines. Rewrite previews identify each match's path, line, and exact end-exclusive byte range, followed by removed `-` lines and replacement `+` lines; surrounding text stays unchanged and files are never written. Overlapping matches are independent previews, not a 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.
- Search accepts up to 4 KiB each for pattern and rewrite, 256 KiB per complete UTF-8 file, 10,000 visited files, and 128 MiB of input 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. Oversized, unreadable, non-UTF-8, or overly complex files are skipped and reported through `files_skipped`, `partial`, and `truncated`.
- 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 and regular filesystem I/O cannot be interrupted mid-call; no detached workers are created. Unknown file types are skipped. In mixed-language searches, patterns that cannot compile for a file's language make results partial; use an explicit language for strict pattern validation.
- Outline shares search's complete-file, file-count, scan-byte, and syntax-tree limits. Its bundled rules cover Rust, TypeScript/TSX, JavaScript, Python, Go, Kotlin, Java, and Swift. Languages without bundled outline rules are ignored.

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

- Outline rejects `pattern` and `rewrite`. Its 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). Filters do not select members directly. Name/type filters are capped at 512 bytes.
- Explicit `structure`/`digest` defaults retain internal symbols for both directory and file requests. `names` omits members and computes names only (signatures are still computed for name filtering); `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 any direct members selected by the view. Line and column positions are 1-based; range ends remain exclusive. Byte offsets are omitted.
- `limit` counts top-level entries, not member or source lines (default 100, maximum 500). A parent and its selected members are kept together 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. A step may traverse the entire remaining bounded file tree and collect direct members. Individual library calls (including compilation of fixed bundled rules) and regular filesystem I/O cannot stop mid-call: this is a cooperative deadline, not a strict killable timeout. No child processes or detached workers are created.
- Check `symbols_returned`, `files_skipped`, `partial`, and `truncated` metadata. Byte/entry limits keep only complete earlier entries; deadlines return partial failure. Empty outlines succeed. Project/ancestor `sgconfig.yml`, custom language libraries, and custom outline rules are never loaded. Only approved regular files are read, using the same path and read checks as search.


## URL routing

Choose the narrowest URL-capable tool:

| Need | Tool | Use |
| --- | --- | --- |
| Public unauthenticated plain text, Markdown, or JSON ordinary GET | `read` | Bounded native fetch/readability; no JavaScript or interaction. |
| Public research and page passages | `web` | Search, open, and find; 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. |

This is routing guidance, not a change to runtime boundaries. Native URL reads retain bounded readability extraction for substantial HTML. 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>"}
{"operation":"find","reference":"web:<reference from a result>","query":"Rust","limit":5,"offset":0}
```

- Search and URL open use fixed Exa `/search` and `/contents` endpoints, with environment-only `EXA_API_KEY` and no MCP fallback. Programming research uses the same search operation.
- Query: 1–512 characters after trimming. URL: at most 2,048 UTF-8 bytes. Search results/find passages: 1–10, default 5. Find offset counts skipped matches, defaults to 0, and is capped at 40,000.
- Search-only filters: `domainFilter` accepts at most 20 bare ASCII domains (253 characters per domain, 63 per label); prefix `-` to exclude. No schemes, ports, paths, whitespace, empty labels, or leading/trailing label hyphens. Entries are trimmed. Includes/excludes map to Exa `includeDomains`/`excludeDomains`. `recencyFilter` accepts only `day`, `week`, `month`, or `year`, mapping to `startPublishedDate` at the current UTC date minus 1, 7, 30, or 365 days. It filters publication dates, not crawl freshness. Filters must be omitted or null for open/find; no aliases or query batches.
- Only public HTTP(S) URLs are accepted. Credentials and sensitive URL parameters are rejected. Resolved private/local addresses are rejected before submitting URLs to Exa; returned URLs are checked too. Exa performs its own fetching; this is not the direct, DNS-pinned `read` transport.
- Exa text requests are capped at 10,000 characters per page. Responses are capped at 2 MiB, cached text at 40,000 bytes per page, and tool output at 48 KiB. Connect/request timeouts are 5s/20s; cancellation is checked around network work.
- Search shows source URLs, stable references, and bounded excerpts. Cached open returns the stored text. Find is case-sensitive literal matching and returns bounded surrounding passages with byte positions and pagination metadata. No matches means no matches in the cached excerpt, not necessarily the entire page.
- The runtime stores at most 64 immutable page snapshots, evicting the oldest. Runtime clones share this cache. It is not persisted or restored from sessions; an expired or unknown reference fails explicitly without network work. Reopen the public URL to obtain a new reference.
- Freshness limitation: URL open returns Exa-provided text, not a guaranteed live fetch. Cached open returns the same stored snapshot without refreshing it. Output does not provide publication, crawl, or fetch timestamps; do not infer them or treat a new reference as proof of fresh content.
- Built-in `browser`, `web_extract`, `web_search`, and `code_search` were removed. No ax/dev-browser setup is needed. Browser automation can be supplied separately through skills; skills are unchanged.

## 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 only a bounded aggregate summary, never raw child output. `ffgrep` and `fffind` are dispatch aliases for `grep` and `find`; `parallel_subagents` is not callable.

## Reading files

Use `paths` for known files or resources:

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


- 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`, `:-M`, `:N-M`, `:N+`, `:N+K`, `:raw`, and compounds such as `:raw:50-100`. Selectors apply to the single target string they suffix and override global `offset`/`limit` for that target.
- Each local file remains limited to 1 MiB and UTF-8 text. Without a selector, `offset` and `limit` apply uniformly to every requested path; `limit` defaults to 400 and 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 `find` for fuzzy path discovery or `grep` for content search, then `read` known paths/resources. Legacy `fffind` and `ffgrep` aliases remain accepted.
- 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.


## 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 under cwd. Output stays cwd-relative and slash-normalized.
- `find` is read-only, uses the shared workspace walker, and does not replace `grep` content search.
- 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 in the provider schema, accepts 1–8 exact strings, and uses OR semantics. Runtime/session replay may still use the legacy `pattern` string, but `patterns` and `pattern` cannot be supplied together.
- 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 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 direct Exa REST only, requires environment-only `EXA_API_KEY` for network operations, and has no MCP fallback. Public 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. Compression changes only provider-visible results; local/session raw results remain unchanged. 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 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 `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.

## Provider-visible tool output compression

`capabilities.tools.output_compression.enabled` defaults to `false`. When enabled, recognized `bash`/`shell` output gets deterministic summaries for supported Git status/diff/log and Cargo check/test commands. Unrecognized or uncertain shell forms pass through unchanged; local display and JSONL results stay raw. Failed empty `grep`/`ffgrep` diagnostic normalization is separate from compression. See [Tool output compression](tool-output-compression.md) for exact command forms, exclusions, rules, and examples.

## 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)