# Filesystem Tools Moltis ships six native filesystem tools that agents use for structured, typed file I/O: `Read`, `Write`, `Edit`, `MultiEdit`, `Glob`, and `Grep`. Their schemas match Claude Code exactly so LLMs trained on those tools work without adaptation. See [GitHub #657](https://github.com/moltis-org/moltis/issues/657) for background. Prefer these over shelling out via the `exec` tool running `cat` / `sed` / `rg` — the native tools give the model line-numbered reads, uniqueness- enforced edits, typed error payloads, and structured audit logs. ## Tools ### `Read` Read a file with line-numbered output. ```json { "file_path": "/absolute/path/to/file.rs", "offset": 1, "limit": 2000, "pages": "1-5" } ``` - `file_path` — absolute path (required). Relative paths are rejected. - `offset` — 1-indexed line to start at. Default `1`. - `limit` — max lines to return. Default `2000`. - `pages` — (PDF only) page range to extract, e.g. `"1-5"`, `"3"`, or `"10-20"`. Max 20 pages per request. Returns one of the following typed payloads (the `kind` field is the discriminator the LLM branches on): - `kind: "text"` — happy path. Includes `content` (cat-n style line numbers), `total_lines`, `rendered_lines`, `start_line`, `truncated`. - `kind: "binary"` — file detected as binary. Includes `bytes`. With `binary_policy = "base64"` the payload also carries `base64`. - `kind: "not_found"` — file does not exist. - `kind: "permission_denied"` — read not permitted by filesystem ACLs. - `kind: "too_large"` — file exceeds `max_read_bytes`. - `kind: "not_regular_file"` — path is a directory, fifo, socket, etc. - `kind: "path_denied"` — blocked by `[tools.fs]` allow/deny rules. - `kind: "image"` — file detected as a supported image format (PNG, JPEG, GIF, WebP). Includes `width`, `height`, `format`, and `base64` data. Images are resized and optimized for LLM consumption. - `kind: "pdf"` — file detected as PDF. Includes `text` (extracted content), `pages` (total page count), `rendered_pages`, and `truncated`. Supports optional `pages` parameter (e.g. `"1-5"`) to extract a page range. CRLF files are rendered with `\r` stripped so the line-number column aligns correctly; the file on disk is not modified. ### `Write` Atomically write a file. Parent directories must already exist. Refuses to follow symlinks. ```json { "file_path": "/absolute/path/to/file.rs", "content": "fn main() {}\n" } ``` Returns `{ file_path, bytes_written, checkpoint_id }`. `checkpoint_id` is `null` unless `[tools.fs].checkpoint_before_mutation` is enabled. ### `Edit` Exact-match string replacement. Refuses to edit if `old_string` is not unique in the file unless `replace_all=true` — the uniqueness requirement is the main correctness win over shell `sed`. ```json { "file_path": "/absolute/path/to/file.rs", "old_string": "fn foo()", "new_string": "fn bar()", "replace_all": false } ``` Returns `{ file_path, replacements, replace_all, recovered_via_crlf, checkpoint_id }`. `recovered_via_crlf` is `true` when the tool fell back to CRLF matching for an LF-only `old_string` against a CRLF file. ### `MultiEdit` Apply multiple sequential edits to a single file atomically. Each edit sees the output of the previous. Either all succeed or the file is left untouched. ```json { "file_path": "/absolute/path/to/file.rs", "edits": [ { "old_string": "alpha", "new_string": "ALPHA" }, { "old_string": "beta", "new_string": "BETA" } ] } ``` ### `Glob` Find files matching a glob pattern, sorted by modification time (newest first). Respects `.gitignore` by default. ```json { "pattern": "**/*.rs", "path": "/absolute/path/to/project" } ``` `path` is required unless `[tools.fs].workspace_root` is configured. A relative `path` is rejected. ### `Grep` Regex content search. Walks with the same ignore rules as `Glob`. ```json { "pattern": "fn\\s+main", "path": "/absolute/path/to/project", "output_mode": "content", "glob": "**/*.rs", "-n": true, "-C": 2 } ``` Parameters: `pattern` (regex, required), `path`, `glob`, `type` (`rust` / `py` / `ts` / etc.), `output_mode` (`files_with_matches` / `content` / `count`), `-i` (case-insensitive), `-n` (line numbers), `-A` / `-B` / `-C` (context lines), `multiline`, `head_limit`, `offset`. ## Configuration All fs tools are configured under a single `[tools.fs]` section. Every field is optional and conservative by default — the tools work out of the box with no configuration. ```toml [tools.fs] # Default search root for Glob/Grep when `path` is omitted. Absolute. # workspace_root = "/home/user/projects/my-app" # Absolute path globs the fs tools may touch. Empty = no allowlist. # Evaluated after canonicalization so symlinks can't escape. allow_paths = [] # Deny globs. Deny wins over allow. deny_paths = [] # Per-session Read history + loop detection. Prerequisite for # must_read_before_write. track_reads = false # Refuse Write/Edit/MultiEdit on files the session hasn't Read. # Requires track_reads = true. must_read_before_write = false # When true, Write/Edit/MultiEdit pause for explicit operator approval # before mutating a file. require_approval = true # Read size cap. Files larger than this return a typed "too_large" payload. max_read_bytes = 10485760 # 10 MB # Binary file handling: # "reject" — typed marker without content (default) # "base64" — include base64-encoded bytes in the payload binary_policy = "reject" # Whether Glob/Grep respect .gitignore / .ignore / .git/info/exclude. respect_gitignore = true # When true, Write/Edit/MultiEdit snapshot the target file via the # existing CheckpointManager before mutating, so the pre-edit state can # be restored via the `checkpoint_restore` tool. Off by default because # checkpoints grow with agent activity. checkpoint_before_mutation = false # Adaptive read sizing: when set, Read caps per-call output so a single # call can't consume more than ~20% of the model's working set. # Clamped to [50 KB, 512 KB]. When unset, Read uses a fixed 256 KB cap. # context_window_tokens = 200000 # e.g. Claude Sonnet ``` `require_approval` reuses the existing approval queue and WebSocket prompting path. If nobody approves the request, the mutation times out instead of landing silently. ## Policy Integration The `[tools.policy]` allow/deny list gates access by tool name, not by file path. You can make an agent read-only without touching fs-level policy: ```toml [tools.policy] deny = ["Write", "Edit", "MultiEdit"] ``` The agent retains `Read`, `Glob`, and `Grep` — no need to deny the shell tool wholesale and lose every other capability. File-path allow/deny lives in `[tools.fs]`. Layer both for fine-grained control. Example: a code-reviewer agent that can read the project tree but can't touch anything outside it: ```toml [tools.policy] allow = ["exec", "browser", "memory", "Read", "Glob", "Grep"] [tools.fs] workspace_root = "/home/user/project" allow_paths = ["/home/user/project/**"] deny_paths = ["/home/user/project/.env*", "/home/user/project/secrets/**"] respect_gitignore = true require_approval = true ``` ## Managed Files [Managed Files](managed-files.md) are a storage surface, not another native filesystem tool. `Read`, `Write`, `Edit`, `MultiEdit`, `Glob`, and `Grep` can operate on that surface only when their normal tool policy and path policy allow it. Use the host path reported as `Host: files_dir=...` when filesystem tools run on the gateway host. In Docker, Podman, or Apple Container sandboxes, use `/home/sandbox/files`; `tools.exec.sandbox.managed_files_mount` determines whether that path is unavailable, read-only, or read-write. `MOLTIS_FILES_DIR` provides the corresponding path to `exec`. Files placed there are not automatically read, indexed as memory, or added to the prompt. The path is advertised so the agent can access a requested file deliberately. ## Structured Audit Every tool invocation is a structured event through moltis's existing tracing layer and the `moltis_tool_executions_total` / `moltis_tool_execution_errors_total` metrics. Writes appear in traces as structured key/value pairs — `tool=Write file_path=... bytes=... outcome=ok` — dramatically easier to review than an opaque shell command string, and the second big win (alongside model-quality improvements) that motivated #657. ## Related - [Checkpoints](checkpoints.md) — pairs with `checkpoint_before_mutation` for opt-in pre-edit snapshots. - [Hooks](hooks.md) — `BeforeToolCall` and `ToolResultPersist` receive structured payloads for each fs tool call, so policy hooks can inspect typed parameters instead of parsing shell strings. - [Sandbox](sandbox.md) — fs tools route through the sandbox when the session is sandboxed. If no real sandbox backend is available, Moltis warns at startup and the tools operate on the gateway host.